diff --git a/.editorconfig b/.editorconfig index b5f39e6a..ea7e95b2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -78,9 +78,9 @@ dotnet_remove_unnecessary_suppression_exclusions = none [*.cs] # var preferences -csharp_style_var_elsewhere = false:silent -csharp_style_var_for_built_in_types = false:silent -csharp_style_var_when_type_is_apparent = false:silent +csharp_style_var_elsewhere = false:warning +csharp_style_var_for_built_in_types = false:warning +csharp_style_var_when_type_is_apparent = true:warning # Expression-bodied members csharp_style_expression_bodied_accessors = true:silent diff --git a/.gitignore b/.gitignore index 083abaae..c5d3ff82 100644 --- a/.gitignore +++ b/.gitignore @@ -452,4 +452,4 @@ $RECYCLE.BIN/ !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json -Tunny/Python/ \ No newline at end of file +Tunny/Lib/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index d25d2f0f..6372b9e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,34 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p Please see [here](https://github.com/hrntsm/Tunny/releases) for the data released for each version. +## [0.2.0] -2022-05-02 + +### Added + +- Restore progressbar +- Support Galapagos genepool input +- Result reflect button + - This feature reflecting multi objective optimization result to slider & genepool to use input model number. + - if input multi model number, the first one is reflect and popup notification about this. +- Support grid sampler + +### Changed + +- Restore feature was made asynchronous. +- Visualize graph axis name now use input objective's nickname. +- Update supported Rhino version to 7.13. +- Disable optimize window resize. + +### Fixed + +- Optimization does not stop when the value of Objective is null. + - When the objective values is null, optimizer try to get another variable and resolve solution. + - If it is 10 trial to get objectives in 1 optimize loop, optimizer throw error. +- Enable visualize param importances function + - this function need sklearn, but tunny's python package doesn't include it. +- Optimize window UI is broken when using Hi-DPI environment. + - Support multi DPI + ## [0.1.1] -2022-04-17 ### Fixed diff --git a/README.md b/README.md index 733c18c8..183b65ea 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Tunny also support yak. So you can find Tunny in Rhinoceros package manager. ### Quick usage -![tunny](https://user-images.githubusercontent.com/23289252/163386009-c60e529e-20d1-4314-b9f5-df8bed3c791e.gif) +![tunny](https://user-images.githubusercontent.com/23289252/166186417-7541ccb9-efa0-4569-a068-373ebde1c0ed.gif) ### Component location @@ -46,31 +46,31 @@ Tunny can be found in the same Params tab as Galapagos under Util if it has been #### Variables -Connect a NumberSlider to Variables. No other components are supported. +This component support Number slider & GenePool. Optimization is performed when this value is changed by Tunny. It is recommended that components be given nicknames, as this makes it easier to understand the resulting process. Here it is named x, y, z. -![image](https://user-images.githubusercontent.com/23289252/163378057-3c0a6a84-4dd2-4d2a-a55d-3202f9abc8bf.png) +![image](https://user-images.githubusercontent.com/23289252/166185821-4b3da178-068b-444a-9d3f-9ee791c533b1.png) #### Objectives -Optimization is performed to minimize the value input here. Multi-objective optimization is also supported. +Optimization is performed to minimize the value input here. Multi-objective optimization is also supported. -For multi-objective optimization, put the target values as a list in one Number component. Multiple Number components are not supported. +Each objective value have to be separated to a number component. +It is recommended to set nickname like input variables. -![image](https://user-images.githubusercontent.com/23289252/163378644-e066dfa8-c36d-4a56-92dd-206dff5eed92.png) +![image](https://user-images.githubusercontent.com/23289252/166185782-3d5ddb69-5912-4b65-8b59-c20f0f1cd6b2.png) #### ModelMesh This input is optional. Mesh input is supported as a function to save the model during optimization. -If multiple meshes are entered as a list, only the first one will be saved. - +If multiple meshes are entered as a list, only the first one will be saved. Input of large size meshes is deprecated because it makes the analysis heavier. -![image](https://user-images.githubusercontent.com/23289252/163379419-40368cc4-8abd-40d0-94ca-d0a468796c57.png) +![image](https://user-images.githubusercontent.com/23289252/166185101-c82d1610-03a5-4906-920c-4ef33508716c.png) ### Optimization Window @@ -94,8 +94,11 @@ Values that can be set and their meanings are as follows. 1. NSGA-II (Genetic algorithm) 1. CMA-ES (Evolution strategy) 1. Random + 1. Grid - Number of trial - This number of trials will be performed. + - If the grid sampler is selected, the calculation is performed by dividing each entered Variable by this number. + - **Note** that the number of calculations is (Number of trial) to the power of (Number of Variable). - Load if study file exists - If the checkbox is checked and a file of optimization results is available, the results of the training will be used to perform ongoing optimization. - Study Name @@ -105,7 +108,7 @@ Values that can be set and their meanings are as follows. #### Result Tab -![image](https://user-images.githubusercontent.com/23289252/163382006-3cb37a7e-ff38-4ced-8227-7c06a0621cd3.png) +![image](https://user-images.githubusercontent.com/23289252/166185559-a5e64659-df48-4777-85dc-7ed01f3752b7.png) Values that can be set and their meanings are as follows. @@ -128,6 +131,7 @@ Values that can be set and their meanings are as follows. - The model with the number entered here is restored from the optimization results file and is the output of the component. - The model number matches the tree structure of the output. - -1 is input, the results of all models on the Pareto front will be the main focus. + - Clicking the Reflect button will cause Grasshopper to reflect the results of the model number inputted. ## Contact information diff --git a/Sample/sample.gh b/Sample/sample.gh index 668ab464..9e8af3f9 100644 Binary files a/Sample/sample.gh and b/Sample/sample.gh differ diff --git a/Tunny/Component/TunnyAttributes.cs b/Tunny/Component/TunnyAttributes.cs index 0eb7e963..640c75c4 100644 --- a/Tunny/Component/TunnyAttributes.cs +++ b/Tunny/Component/TunnyAttributes.cs @@ -53,7 +53,7 @@ private void DrawVariableWire(GH_Canvas canvas, Graphics graphics) PointF p1 = param.Attributes.InputGrip; int wireWidth = 1; - Color wireColor = Color.FromArgb(Convert.ToInt32("3300008B", 16)); + var wireColor = Color.FromArgb(Convert.ToInt32("3300008B", 16)); if (Owner.Attributes.Selected) { wireWidth = 3; @@ -88,7 +88,7 @@ private void DrawObjectiveWire(GH_Canvas canvas, Graphics graphics) PointF p1 = param.Attributes.InputGrip; int wireWidth = 2; - Color wireColor = Color.FromArgb(Convert.ToInt32("33008000", 16)); + var wireColor = Color.FromArgb(Convert.ToInt32("33008000", 16)); if (Owner.Attributes.Selected) { wireWidth = 3; @@ -122,7 +122,7 @@ private void DrawModelMeshWire(GH_Canvas canvas, Graphics graphics) PointF p1 = param.Attributes.InputGrip; int wireWidth = 2; - Color wireColor = Color.FromArgb(Convert.ToInt32("338B008B", 16)); + var wireColor = Color.FromArgb(Convert.ToInt32("338B008B", 16)); if (Owner.Attributes.Selected) { wireWidth = 3; diff --git a/Tunny/Optimization/Loop.cs b/Tunny/Optimization/OptimizeLoop.cs similarity index 76% rename from Tunny/Optimization/Loop.cs rename to Tunny/Optimization/OptimizeLoop.cs index 05b98755..18ddbfc8 100644 --- a/Tunny/Optimization/Loop.cs +++ b/Tunny/Optimization/OptimizeLoop.cs @@ -3,6 +3,8 @@ using System.ComponentModel; using System.Linq; +using Grasshopper.Kernel; + using Tunny.Component; using Tunny.Solver; using Tunny.UI; @@ -10,7 +12,7 @@ namespace Tunny.Optimization { - internal static class Loop + internal static class OptimizeLoop { private static BackgroundWorker s_worker; private static TunnyComponent s_component; @@ -19,7 +21,7 @@ internal static class Loop public static string SamplerType; public static string StudyName; - internal static void RunOptimizationLoopMultiple(object sender, DoWorkEventArgs e) + internal static void RunMultiple(object sender, DoWorkEventArgs e) { s_worker = sender as BackgroundWorker; s_component = e.Argument as TunnyComponent; @@ -27,7 +29,7 @@ internal static void RunOptimizationLoopMultiple(object sender, DoWorkEventArgs s_component.GhInOutInstantiate(); double[] result = RunOptimizationLoop(s_worker); - List decimalResults = result.Select(Convert.ToDecimal).ToList(); + var decimalResults = result.Select(Convert.ToDecimal).ToList(); s_component.OptimizationWindow.GrasshopperStatus = OptimizationWindow.GrasshopperStates.RequestSent; s_worker.ReportProgress(100, decimalResults); @@ -43,14 +45,15 @@ internal static void RunOptimizationLoopMultiple(object sender, DoWorkEventArgs private static double[] RunOptimizationLoop(BackgroundWorker worker) { List variables = s_component.GhInOut.Variables; + List objectives = s_component.GhInOut.Objectives; if (worker.CancellationPending) { return new[] { double.NaN }; } - var solver = new Optuna(s_component.GhInOut.ComponentFolder); - Dictionary settings = new Dictionary() + var optunaSolver = new Optuna(s_component.GhInOut.ComponentFolder); + var settings = new Dictionary() { { "nTrials", NTrials }, { "loadIfExists", LoadIfExists }, @@ -60,13 +63,13 @@ private static double[] RunOptimizationLoop(BackgroundWorker worker) { "nObjective", s_component.GhInOut.GetObjectiveValues().Count } }; - bool solverStarted = solver.RunSolver( - variables, EvaluateFunction, "OptunaTPE", settings, "", ""); + bool solverStarted = optunaSolver.RunSolver( + variables, objectives, EvaluateFunction, "OptunaTPE", settings, "", ""); - return solverStarted ? solver.XOpt : new[] { double.NaN }; + return solverStarted ? optunaSolver.XOpt : new[] { double.NaN }; } - public static EvaluatedGHResult EvaluateFunction(IList values, int progress) + private static EvaluatedGHResult EvaluateFunction(IList values, int progress) { s_component.OptimizationWindow.GrasshopperStatus = OptimizationWindow.GrasshopperStates.RequestSent; diff --git a/Tunny/Optimization/RestoreLoop.cs b/Tunny/Optimization/RestoreLoop.cs new file mode 100644 index 00000000..85ed0e2b --- /dev/null +++ b/Tunny/Optimization/RestoreLoop.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Windows.Forms; + +using Grasshopper.Kernel.Data; +using Grasshopper.Kernel.Types; + +using Rhino.FileIO; +using Rhino.Geometry; + +using Tunny.Component; +using Tunny.Solver; +using Tunny.UI; +using Tunny.Util; + +namespace Tunny.Optimization +{ + internal static class RestoreLoop + { + private static BackgroundWorker s_worker; + private static TunnyComponent s_component; + public static string StudyName; + public static string[] NickNames; + public static int[] Indices; + public static string Mode; + + internal static void Run(object sender, DoWorkEventArgs e) + { + s_worker = sender as BackgroundWorker; + s_component = e.Argument as TunnyComponent; + + var modelMesh = new GH_Structure(); + var variables = new GH_Structure(); + var objectives = new GH_Structure(); + + var optunaSolver = new Optuna(s_component.GhInOut.ComponentFolder); + ModelResult[] modelResult = optunaSolver.GetModelResult(Indices, StudyName); + if (modelResult.Length == 0) + { + TunnyMessageBox.Show("There are no restore models. Please check study name.", "Tunny", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + switch (Mode) + { + case "Restore": + for (int i = 0; i < modelResult.Length; i++) + { + SetVariables(variables, modelResult[i], NickNames); + SetObjectives(objectives, modelResult[i]); + SetModelMesh(modelMesh, modelResult[i]); + s_worker.ReportProgress(i * 100 / modelResult.Length); + } + break; + case "Reflect": + if (modelResult.Length > 1) + { + TunnyMessageBox.Show( + "You input multi restore model numbers, but this function only reflect variables to slider or genepool to first one.", + "Tunny", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + } + SetVariables(variables, modelResult[0], NickNames); + SetObjectives(objectives, modelResult[0]); + SetModelMesh(modelMesh, modelResult[0]); + s_worker.ReportProgress(100); + break; + } + + s_component.Variables = variables; + s_component.Objectives = objectives; + s_component.ModelMesh = modelMesh; + s_worker.ReportProgress(100); + + if (s_worker != null) + { + s_worker.CancelAsync(); + } + TunnyMessageBox.Show("Restore completed successfully.", "Tunny"); + } + + private static void SetVariables(GH_Structure objectives, ModelResult model, IEnumerable nickName) + { + foreach (string name in nickName) + { + foreach (KeyValuePair obj in model.Variables.Where(obj => obj.Key == name)) + { + objectives.Append(new GH_Number(obj.Value), new GH_Path(0, model.Number)); + } + } + } + + private static void SetObjectives(GH_Structure objectives, ModelResult model) + { + foreach (double obj in model.Objectives) + { + objectives.Append(new GH_Number(obj), new GH_Path(0, model.Number)); + } + } + + private static void SetModelMesh(GH_Structure modelMesh, ModelResult model) + { + if (model.Draco == string.Empty) + { + return; + } + var mesh = (Mesh)DracoCompression.DecompressBase64String(model.Draco); + modelMesh.Append(new GH_Mesh(mesh), new GH_Path(0, model.Number)); + } + } +} \ No newline at end of file diff --git a/Tunny/Resources/app.manifest b/Tunny/Resources/app.manifest new file mode 100644 index 00000000..ec3b6c69 --- /dev/null +++ b/Tunny/Resources/app.manifest @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + diff --git a/Tunny/Solver/ISolver.cs b/Tunny/Solver/ISolver.cs index f7b9f73b..847b09f2 100644 --- a/Tunny/Solver/ISolver.cs +++ b/Tunny/Solver/ISolver.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using Grasshopper.Kernel; + using Tunny.Optimization; using Tunny.Util; @@ -12,6 +14,7 @@ internal interface ISolver double[] FxOpt { get; } bool RunSolver( List variables, + List objectives, Func, int, EvaluatedGHResult> evaluate, string preset, Dictionary settings, diff --git a/Tunny/Solver/Optuna.cs b/Tunny/Solver/Optuna.cs index 236ad8cb..4ebbc80c 100644 --- a/Tunny/Solver/Optuna.cs +++ b/Tunny/Solver/Optuna.cs @@ -3,6 +3,8 @@ using System.Linq; using System.Windows.Forms; +using Grasshopper.Kernel; + using Python.Runtime; using Tunny.Optimization; @@ -28,33 +30,35 @@ public Optuna(string componentFolder) public bool RunSolver( List variables, + List objectives, Func, int, EvaluatedGHResult> evaluate, string preset, Dictionary settings, string installFolder, string documentPath) { int dVar = variables.Count; - var lb = new double[dVar]; - var ub = new double[dVar]; - var integer = new bool[dVar]; - var nickName = new string[dVar]; + double[] lb = new double[dVar]; + double[] ub = new double[dVar]; + bool[] integer = new bool[dVar]; + string[] varNickName = new string[dVar]; + string[] objNickName = objectives.Select(x => x.NickName).ToArray(); - for (var i = 0; i < dVar; i++) + for (int i = 0; i < dVar; i++) { lb[i] = Convert.ToDouble(variables[i].LowerBond); ub[i] = Convert.ToDouble(variables[i].UpperBond); integer[i] = variables[i].Integer; - nickName[i] = variables[i].NickName; + varNickName[i] = variables[i].NickName; } EvaluatedGHResult Eval(double[] x, int progress) { - List decimals = x.Select(Convert.ToDecimal).ToList(); + var decimals = x.Select(Convert.ToDecimal).ToList(); return evaluate(decimals, progress); } try { - var tpe = new OptunaAlgorithm(lb, ub, nickName, settings, Eval); + var tpe = new OptunaAlgorithm(lb, ub, varNickName, objNickName, settings, Eval); tpe.Solve(); XOpt = tpe.Get_XOptimum(); FxOpt = tpe.Get_fxOptimum(); @@ -96,17 +100,18 @@ public void ShowResultVisualize(string visualize, string studyName) return; } + string[] nickNames = ((string)study.user_attrs["objective_names"]).Split(','); try { dynamic vis; switch (visualize) { case "contour": - vis = optuna.visualization.plot_contour(study); + vis = optuna.visualization.plot_contour(study, target_name: nickNames[0]); vis.show(); break; case "EDF": - vis = optuna.visualization.plot_edf(study); + vis = optuna.visualization.plot_edf(study, target_name: nickNames[0]); vis.show(); break; case "intermediate values": @@ -114,23 +119,23 @@ public void ShowResultVisualize(string visualize, string studyName) vis.show(); break; case "optimization history": - vis = optuna.visualization.plot_optimization_history(study); + vis = optuna.visualization.plot_optimization_history(study, target_name: nickNames[0]); vis.show(); break; case "parallel coordinate": - vis = optuna.visualization.plot_parallel_coordinate(study); + vis = optuna.visualization.plot_parallel_coordinate(study, targat_name: nickNames[0]); vis.show(); break; case "param importances": - vis = optuna.visualization.plot_param_importances(study); + vis = optuna.visualization.plot_param_importances(study, target_name: nickNames[0]); vis.show(); break; case "pareto front": - vis = optuna.visualization.plot_pareto_front(study); + vis = optuna.visualization.plot_pareto_front(study, target_names: nickNames); vis.show(); break; case "slice": - vis = optuna.visualization.plot_slice(study); + vis = optuna.visualization.plot_slice(study, target_name: nickNames[0]); vis.show(); break; default: @@ -176,7 +181,7 @@ public ModelResult[] GetModelResult(int[] resultNum, string studyName) } else { - for (var i = 0; i < resultNum.Length; i++) + for (int i = 0; i < resultNum.Length; i++) { dynamic trial = study.trials[resultNum[i]]; ParseTrial(modelResult, trial); @@ -190,10 +195,10 @@ public ModelResult[] GetModelResult(int[] resultNum, string studyName) private static void ParseTrial(ICollection modelResult, dynamic trial) { - var values = (double[])trial.@params.values(); - var keys = (string[])trial.@params.keys(); + double[] values = (double[])trial.@params.values(); + string[] keys = (string[])trial.@params.keys(); var variables = new Dictionary(); - for (var i = 0; i < keys.Length; i++) + for (int i = 0; i < keys.Length; i++) { variables.Add(keys[i], values[i]); } @@ -207,12 +212,4 @@ private static void ParseTrial(ICollection modelResult, dynamic tri }); } } - - public class ModelResult - { - public int Number { get; set; } - public string Draco { get; set; } - public Dictionary Variables { get; set; } - public double[] Objectives { get; set; } - } } \ No newline at end of file diff --git a/Tunny/Solver/OptunaAlgorithm.cs b/Tunny/Solver/OptunaAlgorithm.cs index 1bd11be4..842ed39c 100644 --- a/Tunny/Solver/OptunaAlgorithm.cs +++ b/Tunny/Solver/OptunaAlgorithm.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text; using Python.Runtime; @@ -11,20 +12,22 @@ public class OptunaAlgorithm { private double[] Lb { get; set; } private double[] Ub { get; set; } - private string[] NickName { get; set; } + private string[] VarNickName { get; set; } + private string[] ObjNickName { get; set; } private Dictionary Settings { get; set; } private Func EvalFunc { get; set; } private double[] XOpt { get; set; } private double[] FxOpt { get; set; } public OptunaAlgorithm( - double[] lb, double[] ub, string[] nickName, + double[] lb, double[] ub, string[] varNickName, string[] objNickName, Dictionary settings, Func evalFunc) { Lb = lb; Ub = ub; - NickName = nickName; + VarNickName = varNickName; + ObjNickName = objNickName; Settings = settings; EvalFunc = evalFunc; } @@ -61,6 +64,20 @@ public void Solve() case "NSGA-II": sampler = optuna.samplers.NSGAIISampler(); break; + case "Grid": + var searchSpace = new Dictionary>(); + for (int i = 0; i < n; i++) + { + var numSpace = new List(); + for (int j = 0; j < nTrials; j++) + { + numSpace.Add(Lb[i] + (Ub[i] - Lb[i]) * j / (nTrials - 1)); + } + searchSpace.Add(VarNickName[i], numSpace); + } + nTrials = (int)Math.Pow(nTrials, n); + sampler = optuna.samplers.GridSampler(searchSpace); + break; default: sampler = optuna.samplers.TPESampler(); break; @@ -74,32 +91,66 @@ public void Solve() directions: directions ); - var xTest = new double[n]; + var name = new StringBuilder(); + foreach (string objName in ObjNickName) + { + name.Append(objName + ","); + } + name.Remove(name.Length - 1, 1); + study.set_user_attr("objective_names", name.ToString()); + + double[] xTest = new double[n]; var result = new EvaluatedGHResult(); for (int i = 0; i < nTrials; i++) { int progress = i * 100 / nTrials; dynamic trial = study.ask(); - for (int j = 0; j < n; j++) + + //TODO: Is this the correct way to handle the case of null? + //Other than TPE, the value is returned at random when retrying, so the value will be anything but null. + //TPEs, on the other hand, search for a specific location determined by GP, + //so the value tends to remain the same even after retries and there is no way to get out. + int nullCount = 0; + while (nullCount < 10) { - xTest[j] = trial.suggest_uniform(NickName[j], Lb[j], Ub[j]); + for (int j = 0; j < n; j++) + { + xTest[j] = trial.suggest_uniform(VarNickName[j], Lb[j], Ub[j]); + } + result = EvalFunc(xTest, progress); + + if (result.ObjectiveValues.Contains(double.NaN)) + { + trial = study.ask(); + nullCount++; + } + else + { + break; + } } - result = EvalFunc(xTest, progress); trial.set_user_attr("geometry", result.ModelDraco); - study.tell(trial, result.ObjectiveValues.ToArray()); + try + { + study.tell(trial, result.ObjectiveValues.ToArray()); + } + catch + { + break; + } } if (nObjective == 1) { - var values = (double[])study.best_params.values(); - var keys = (string[])study.best_params.keys(); - var opt = new double[NickName.Length]; + double[] values = (double[])study.best_params.values(); + string[] keys = (string[])study.best_params.keys(); + double[] opt = new double[VarNickName.Length]; - for (int i = 0; i < NickName.Length; i++) + for (int i = 0; i < VarNickName.Length; i++) { for (int j = 0; j < keys.Length; j++) { - if (keys[j] == NickName[i]) + if (keys[j] == VarNickName[i]) { opt[i] = values[j]; } diff --git a/Tunny/Tunny.csproj b/Tunny/Tunny.csproj index 33c5607e..2a825658 100644 --- a/Tunny/Tunny.csproj +++ b/Tunny/Tunny.csproj @@ -2,20 +2,26 @@ net48 - 0.1.1 + 0.2.0 Tunny Tunny is an optimization component wrapped in optuna. .gha hrntsm true Tunny - false + true + Resources\TunnyIcon.ico + - + + + Lib\GalapagosComponents.dll + false + @@ -41,7 +47,7 @@ - + \ No newline at end of file diff --git a/Tunny/UI/MessageBox.cs b/Tunny/UI/MessageBox.cs index 10ebc481..0d80d948 100644 --- a/Tunny/UI/MessageBox.cs +++ b/Tunny/UI/MessageBox.cs @@ -6,7 +6,7 @@ class TunnyMessageBox { public static void Show(string message, string caption, MessageBoxButtons buttons = MessageBoxButtons.OK, MessageBoxIcon icon = MessageBoxIcon.Information) { - using (Form f = new Form()) + using (var f = new Form()) { f.Owner = Grasshopper.Instances.DocumentEditor; f.TopMost = true; diff --git a/Tunny/UI/OptimizationWindow.Designer.cs b/Tunny/UI/OptimizationWindow.Designer.cs index 866f65a7..86523355 100644 --- a/Tunny/UI/OptimizationWindow.Designer.cs +++ b/Tunny/UI/OptimizationWindow.Designer.cs @@ -30,13 +30,13 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(OptimizationWindow)); - this.runOptimizeButton = new System.Windows.Forms.Button(); - this.backgroundWorkerSolver = new System.ComponentModel.BackgroundWorker(); - this.stopButton = new System.Windows.Forms.Button(); + this.optimizeRunButton = new System.Windows.Forms.Button(); + this.optimizeBackgroundWorker = new System.ComponentModel.BackgroundWorker(); + this.optimizeStopButton = new System.Windows.Forms.Button(); this.nTrialNumUpDown = new System.Windows.Forms.NumericUpDown(); this.nTrialText = new System.Windows.Forms.Label(); this.loadIfExistsCheckBox = new System.Windows.Forms.CheckBox(); - this.progressBar = new System.Windows.Forms.ProgressBar(); + this.optimizeProgressBar = new System.Windows.Forms.ProgressBar(); this.samplerComboBox = new System.Windows.Forms.ComboBox(); this.SamplerTypeText = new System.Windows.Forms.Label(); this.studyNameLabel = new System.Windows.Forms.Label(); @@ -44,7 +44,10 @@ private void InitializeComponent() this.optimizeTabControl = new System.Windows.Forms.TabControl(); this.optimizeTabPage = new System.Windows.Forms.TabPage(); this.resultTabPage = new System.Windows.Forms.TabPage(); - this.RestoreButton = new System.Windows.Forms.Button(); + this.restoreReflectButton = new System.Windows.Forms.Button(); + this.restoreStopButton = new System.Windows.Forms.Button(); + this.restoreProgressBar = new System.Windows.Forms.ProgressBar(); + this.restoreRunButton = new System.Windows.Forms.Button(); this.restoreModelNumTextBox = new System.Windows.Forms.TextBox(); this.restoreModelLabel = new System.Windows.Forms.Label(); this.openResultFolderButton = new System.Windows.Forms.Button(); @@ -52,33 +55,35 @@ private void InitializeComponent() this.VisualizeButton = new System.Windows.Forms.Button(); this.visualizeTypeLabel = new System.Windows.Forms.Label(); this.visualizeTypeComboBox = new System.Windows.Forms.ComboBox(); + this.restoreBackgroundWorker = new System.ComponentModel.BackgroundWorker(); ((System.ComponentModel.ISupportInitialize)(this.nTrialNumUpDown)).BeginInit(); this.optimizeTabControl.SuspendLayout(); this.optimizeTabPage.SuspendLayout(); this.resultTabPage.SuspendLayout(); this.SuspendLayout(); // - // runOptimizeButton - // - this.runOptimizeButton.Location = new System.Drawing.Point(13, 168); - this.runOptimizeButton.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.runOptimizeButton.Name = "runOptimizeButton"; - this.runOptimizeButton.Size = new System.Drawing.Size(120, 29); - this.runOptimizeButton.TabIndex = 0; - this.runOptimizeButton.Text = "RunOptimize"; - this.runOptimizeButton.UseVisualStyleBackColor = true; - this.runOptimizeButton.Click += new System.EventHandler(this.ButtonRunOptimize_Click); - // - // stopButton - // - this.stopButton.Location = new System.Drawing.Point(155, 168); - this.stopButton.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.stopButton.Name = "stopButton"; - this.stopButton.Size = new System.Drawing.Size(87, 29); - this.stopButton.TabIndex = 1; - this.stopButton.Text = "Stop"; - this.stopButton.UseVisualStyleBackColor = true; - this.stopButton.Click += new System.EventHandler(this.ButtonStop_Click); + // optimizeRunButton + // + this.optimizeRunButton.Location = new System.Drawing.Point(13, 168); + this.optimizeRunButton.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); + this.optimizeRunButton.Name = "optimizeRunButton"; + this.optimizeRunButton.Size = new System.Drawing.Size(120, 29); + this.optimizeRunButton.TabIndex = 0; + this.optimizeRunButton.Text = "RunOptimize"; + this.optimizeRunButton.UseVisualStyleBackColor = true; + this.optimizeRunButton.Click += new System.EventHandler(this.OptimizeRunButton_Click); + // + // optimizeStopButton + // + this.optimizeStopButton.Enabled = false; + this.optimizeStopButton.Location = new System.Drawing.Point(155, 168); + this.optimizeStopButton.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); + this.optimizeStopButton.Name = "optimizeStopButton"; + this.optimizeStopButton.Size = new System.Drawing.Size(87, 29); + this.optimizeStopButton.TabIndex = 1; + this.optimizeStopButton.Text = "Stop"; + this.optimizeStopButton.UseVisualStyleBackColor = true; + this.optimizeStopButton.Click += new System.EventHandler(this.OptimizeStopButton_Click); // // nTrialNumUpDown // @@ -121,13 +126,13 @@ private void InitializeComponent() this.loadIfExistsCheckBox.Text = "Load if study file exists"; this.loadIfExistsCheckBox.UseVisualStyleBackColor = true; // - // progressBar + // optimizeProgressBar // - this.progressBar.Location = new System.Drawing.Point(13, 204); - this.progressBar.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.progressBar.Name = "progressBar"; - this.progressBar.Size = new System.Drawing.Size(227, 29); - this.progressBar.TabIndex = 6; + this.optimizeProgressBar.Location = new System.Drawing.Point(13, 205); + this.optimizeProgressBar.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); + this.optimizeProgressBar.Name = "optimizeProgressBar"; + this.optimizeProgressBar.Size = new System.Drawing.Size(227, 28); + this.optimizeProgressBar.TabIndex = 6; // // samplerComboBox // @@ -136,7 +141,8 @@ private void InitializeComponent() "TPE", "NSGA-II", "CMA-ES", - "Random"}); + "Random", + "Grid"}); this.samplerComboBox.Location = new System.Drawing.Point(99, 8); this.samplerComboBox.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.samplerComboBox.Name = "samplerComboBox"; @@ -186,11 +192,11 @@ private void InitializeComponent() this.optimizeTabPage.Controls.Add(this.studyNameTextBox); this.optimizeTabPage.Controls.Add(this.samplerComboBox); this.optimizeTabPage.Controls.Add(this.studyNameLabel); - this.optimizeTabPage.Controls.Add(this.runOptimizeButton); + this.optimizeTabPage.Controls.Add(this.optimizeRunButton); this.optimizeTabPage.Controls.Add(this.SamplerTypeText); - this.optimizeTabPage.Controls.Add(this.stopButton); + this.optimizeTabPage.Controls.Add(this.optimizeStopButton); this.optimizeTabPage.Controls.Add(this.nTrialNumUpDown); - this.optimizeTabPage.Controls.Add(this.progressBar); + this.optimizeTabPage.Controls.Add(this.optimizeProgressBar); this.optimizeTabPage.Controls.Add(this.nTrialText); this.optimizeTabPage.Controls.Add(this.loadIfExistsCheckBox); this.optimizeTabPage.Location = new System.Drawing.Point(4, 24); @@ -204,7 +210,10 @@ private void InitializeComponent() // // resultTabPage // - this.resultTabPage.Controls.Add(this.RestoreButton); + this.resultTabPage.Controls.Add(this.restoreReflectButton); + this.resultTabPage.Controls.Add(this.restoreStopButton); + this.resultTabPage.Controls.Add(this.restoreProgressBar); + this.resultTabPage.Controls.Add(this.restoreRunButton); this.resultTabPage.Controls.Add(this.restoreModelNumTextBox); this.resultTabPage.Controls.Add(this.restoreModelLabel); this.resultTabPage.Controls.Add(this.openResultFolderButton); @@ -221,28 +230,57 @@ private void InitializeComponent() this.resultTabPage.Text = "Result"; this.resultTabPage.UseVisualStyleBackColor = true; // - // RestoreButton - // - this.RestoreButton.Location = new System.Drawing.Point(172, 173); - this.RestoreButton.Name = "RestoreButton"; - this.RestoreButton.Size = new System.Drawing.Size(75, 23); - this.RestoreButton.TabIndex = 7; - this.RestoreButton.Text = "Restore"; - this.RestoreButton.UseVisualStyleBackColor = true; - this.RestoreButton.Click += new System.EventHandler(this.RestoreButton_Click); + // restoreReflectButton + // + this.restoreReflectButton.Location = new System.Drawing.Point(152, 177); + this.restoreReflectButton.Name = "restoreReflectButton"; + this.restoreReflectButton.Size = new System.Drawing.Size(62, 23); + this.restoreReflectButton.TabIndex = 10; + this.restoreReflectButton.Text = "Reflect"; + this.restoreReflectButton.UseVisualStyleBackColor = true; + this.restoreReflectButton.Click += new System.EventHandler(this.RestoreReflectButton_Click); + // + // restoreStopButton + // + this.restoreStopButton.Enabled = false; + this.restoreStopButton.Location = new System.Drawing.Point(96, 177); + this.restoreStopButton.Name = "restoreStopButton"; + this.restoreStopButton.Size = new System.Drawing.Size(50, 23); + this.restoreStopButton.TabIndex = 9; + this.restoreStopButton.Text = "Stop"; + this.restoreStopButton.UseVisualStyleBackColor = true; + this.restoreStopButton.Click += new System.EventHandler(this.RestoreStopButton_Click); + // + // restoreProgressBar + // + this.restoreProgressBar.Location = new System.Drawing.Point(9, 207); + this.restoreProgressBar.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); + this.restoreProgressBar.Name = "restoreProgressBar"; + this.restoreProgressBar.Size = new System.Drawing.Size(235, 27); + this.restoreProgressBar.TabIndex = 8; + // + // restoreRunButton + // + this.restoreRunButton.Location = new System.Drawing.Point(21, 177); + this.restoreRunButton.Name = "restoreRunButton"; + this.restoreRunButton.Size = new System.Drawing.Size(69, 23); + this.restoreRunButton.TabIndex = 7; + this.restoreRunButton.Text = "Restore"; + this.restoreRunButton.UseVisualStyleBackColor = true; + this.restoreRunButton.Click += new System.EventHandler(this.RestoreRunButton_Click); // // restoreModelNumTextBox // - this.restoreModelNumTextBox.Location = new System.Drawing.Point(6, 174); + this.restoreModelNumTextBox.Location = new System.Drawing.Point(9, 148); this.restoreModelNumTextBox.Name = "restoreModelNumTextBox"; - this.restoreModelNumTextBox.Size = new System.Drawing.Size(158, 23); + this.restoreModelNumTextBox.Size = new System.Drawing.Size(172, 23); this.restoreModelNumTextBox.TabIndex = 6; this.restoreModelNumTextBox.Text = "-1"; // // restoreModelLabel // this.restoreModelLabel.AutoSize = true; - this.restoreModelLabel.Location = new System.Drawing.Point(7, 156); + this.restoreModelLabel.Location = new System.Drawing.Point(3, 130); this.restoreModelLabel.Name = "restoreModelLabel"; this.restoreModelLabel.Size = new System.Drawing.Size(162, 15); this.restoreModelLabel.TabIndex = 5; @@ -310,8 +348,8 @@ private void InitializeComponent() // // OptimizationWindow // - this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.ClientSize = new System.Drawing.Size(285, 298); this.Controls.Add(this.optimizeTabControl); this.Font = new System.Drawing.Font("Meiryo UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); @@ -333,12 +371,12 @@ private void InitializeComponent() #endregion - private System.Windows.Forms.Button runOptimizeButton; - private System.ComponentModel.BackgroundWorker backgroundWorkerSolver; - private System.Windows.Forms.Button stopButton; + private System.Windows.Forms.Button optimizeRunButton; + private System.ComponentModel.BackgroundWorker optimizeBackgroundWorker; + private System.Windows.Forms.Button optimizeStopButton; private System.Windows.Forms.NumericUpDown nTrialNumUpDown; private System.Windows.Forms.CheckBox loadIfExistsCheckBox; - private System.Windows.Forms.ProgressBar progressBar; + private System.Windows.Forms.ProgressBar optimizeProgressBar; private System.Windows.Forms.ComboBox samplerComboBox; private System.Windows.Forms.Label SamplerTypeText; private System.Windows.Forms.Label nTrialText; @@ -352,9 +390,13 @@ private void InitializeComponent() private System.Windows.Forms.ComboBox visualizeTypeComboBox; private System.Windows.Forms.Button clearResultButton; private System.Windows.Forms.Button openResultFolderButton; - private System.Windows.Forms.Button RestoreButton; + private System.Windows.Forms.Button restoreRunButton; private System.Windows.Forms.TextBox restoreModelNumTextBox; private System.Windows.Forms.Label restoreModelLabel; + private System.Windows.Forms.ProgressBar restoreProgressBar; + private System.ComponentModel.BackgroundWorker restoreBackgroundWorker; + private System.Windows.Forms.Button restoreStopButton; + private System.Windows.Forms.Button restoreReflectButton; } } diff --git a/Tunny/UI/OptimizationWindow.cs b/Tunny/UI/OptimizationWindow.cs index e75e526b..305a84e6 100644 --- a/Tunny/UI/OptimizationWindow.cs +++ b/Tunny/UI/OptimizationWindow.cs @@ -7,11 +7,6 @@ using System.Windows.Forms; using Grasshopper.GUI; -using Grasshopper.Kernel.Data; -using Grasshopper.Kernel.Types; - -using Rhino.FileIO; -using Rhino.Geometry; using Tunny.Component; using Tunny.Optimization; @@ -38,20 +33,17 @@ public OptimizationWindow(TunnyComponent component) samplerComboBox.SelectedIndex = 0; visualizeTypeComboBox.SelectedIndex = 3; - backgroundWorkerSolver.DoWork += Loop.RunOptimizationLoopMultiple; - backgroundWorkerSolver.ProgressChanged += ProgressChangedHandler; - backgroundWorkerSolver.RunWorkerCompleted += ButtonStop_Click; - backgroundWorkerSolver.WorkerReportsProgress = true; - backgroundWorkerSolver.WorkerSupportsCancellation = true; - } - - private void ProgressChangedHandler(object sender, ProgressChangedEventArgs e) - { - var parameters = (IList)e.UserState; - UpdateGrasshopper(parameters); - - progressBar.Value = e.ProgressPercentage; - progressBar.Update(); + optimizeBackgroundWorker.DoWork += OptimizeLoop.RunMultiple; + optimizeBackgroundWorker.ProgressChanged += OptimizeProgressChangedHandler; + optimizeBackgroundWorker.RunWorkerCompleted += OptimizeStopButton_Click; + optimizeBackgroundWorker.WorkerReportsProgress = true; + optimizeBackgroundWorker.WorkerSupportsCancellation = true; + + restoreBackgroundWorker.DoWork += RestoreLoop.Run; + restoreBackgroundWorker.ProgressChanged += RestoreProgressChangedHandler; + restoreBackgroundWorker.RunWorkerCompleted += RestoreStopButton_Click; + restoreBackgroundWorker.WorkerReportsProgress = true; + restoreBackgroundWorker.WorkerSupportsCancellation = true; } private void UpdateGrasshopper(IList parameters) @@ -66,41 +58,9 @@ private void UpdateGrasshopper(IList parameters) private void OptimizationWindow_Load(object sender, EventArgs e) { - - } - - private void ButtonRunOptimize_Click(object sender, EventArgs e) - { - GH_DocumentEditor ghCanvas = Owner as GH_DocumentEditor; - ghCanvas.DisableUI(); - - runOptimizeButton.Enabled = false; - Loop.NTrials = (int)nTrialNumUpDown.Value; - Loop.LoadIfExists = loadIfExistsCheckBox.Checked; - Loop.SamplerType = samplerComboBox.Text; - Loop.StudyName = studyNameTextBox.Text; - - if (_component.GhInOut.GetObjectiveValues().Count > 1 && (samplerComboBox.Text == "CMA-ES" || samplerComboBox.Text == "Random")) - { - MessageBox.Show("This sampler does not support multiple objectives optimization.", "Tunny", MessageBoxButtons.OK, MessageBoxIcon.Error); - ghCanvas.EnableUI(); - runOptimizeButton.Enabled = true; - return; - } - - backgroundWorkerSolver.RunWorkerAsync(_component); - - stopButton.Enabled = true; - } - - private void ButtonStop_Click(object sender, EventArgs e) - { - runOptimizeButton.Enabled = true; - stopButton.Enabled = false; - - //Enable GUI - var ghCanvas = Owner as GH_DocumentEditor; - ghCanvas?.EnableUI(); + FormBorderStyle = FormBorderStyle.FixedSingle; + MaximizeBox = false; + MinimizeBox = false; } private void VisualizeButton_Click(object sender, EventArgs e) @@ -119,63 +79,138 @@ private void ClearResultButton_Click(object sender, EventArgs e) File.Delete(_component.GhInOut.ComponentFolder + "/Tunny_Opt_Result.db"); } - private void RestoreButton_Click(object sender, EventArgs e) + private void FormClosingXButton(object sender, FormClosingEventArgs e) { - var modelMesh = new GH_Structure(); - var variables = new GH_Structure(); - var objectives = new GH_Structure(); - var nickName = _component.GhInOut.Sliders.Select(x => x.NickName).ToArray(); + var ghCanvas = Owner as GH_DocumentEditor; + ghCanvas?.EnableUI(); - var optuna = new Optuna(_component.GhInOut.ComponentFolder); - string studyName = studyNameTextBox.Text; + if (optimizeBackgroundWorker != null) + { + optimizeBackgroundWorker.CancelAsync(); + } - int[] num = restoreModelNumTextBox.Text.Split(',').Select(int.Parse).ToArray(); - ModelResult[] modelResult = optuna.GetModelResult(num, studyName); - foreach (ModelResult model in modelResult) + if (restoreBackgroundWorker != null) { - SetVariables(variables, model, nickName); - SetObjectives(objectives, model); - SetModelMesh(modelMesh, model); + restoreBackgroundWorker.CancelAsync(); } - _component.Variables = variables; - _component.Objectives = objectives; - _component.ModelMesh = modelMesh; - _component.ExpireSolution(true); } - private static void SetVariables(GH_Structure objectives, ModelResult model, IEnumerable nickName) + private void RestoreRunButton_Click(object sender, EventArgs e) { - foreach (string name in nickName) - { - foreach (var obj in model.Variables.Where(obj => obj.Key == name)) - { - objectives.Append(new GH_Number(obj.Value), new GH_Path(0, model.Number)); - } - } + optimizeRunButton.Enabled = false; + optimizeStopButton.Enabled = true; + + RestoreLoop.StudyName = studyNameTextBox.Text; + RestoreLoop.Mode = "Restore"; + RestoreLoop.NickNames = _component.GhInOut.Variables.Select(x => x.NickName).ToArray(); + RestoreLoop.Indices = restoreModelNumTextBox.Text.Split(',').Select(int.Parse).ToArray(); + + restoreBackgroundWorker.RunWorkerAsync(_component); } - private static void SetObjectives(GH_Structure objectives, ModelResult model) + private void RestoreStopButton_Click(object sender, EventArgs e) { - foreach (double obj in model.Objectives) + optimizeRunButton.Enabled = true; + optimizeStopButton.Enabled = false; + + if (restoreBackgroundWorker != null) + { + restoreBackgroundWorker.CancelAsync(); + } + switch (RestoreLoop.Mode) { - objectives.Append(new GH_Number(obj), new GH_Path(0, model.Number)); + case "Restore": + _component.ExpireSolution(true); + break; + case "Reflect": + var decimalVar = _component.Variables.FlattenData() + .Select(x => (decimal)x.Value).ToList(); + _component.GhInOut.NewSolution(decimalVar); + break; } } - private static void SetModelMesh(GH_Structure modelMesh, ModelResult model) + private void RestoreReflectButton_Click(object sender, EventArgs e) + { + optimizeRunButton.Enabled = false; + optimizeStopButton.Enabled = true; + + RestoreLoop.StudyName = studyNameTextBox.Text; + RestoreLoop.Mode = "Reflect"; + RestoreLoop.NickNames = _component.GhInOut.Variables.Select(x => x.NickName).ToArray(); + RestoreLoop.Indices = restoreModelNumTextBox.Text.Split(',').Select(int.Parse).ToArray(); + + restoreBackgroundWorker.RunWorkerAsync(_component); + + } + + private void RestoreProgressChangedHandler(object sender, ProgressChangedEventArgs e) { - if (model.Draco == string.Empty) + restoreProgressBar.Value = e.ProgressPercentage; + restoreProgressBar.Update(); + } + + private void OptimizeRunButton_Click(object sender, EventArgs e) + { + var ghCanvas = Owner as GH_DocumentEditor; + ghCanvas.DisableUI(); + + optimizeRunButton.Enabled = false; + OptimizeLoop.NTrials = (int)nTrialNumUpDown.Value; + OptimizeLoop.LoadIfExists = loadIfExistsCheckBox.Checked; + OptimizeLoop.SamplerType = samplerComboBox.Text; + OptimizeLoop.StudyName = studyNameTextBox.Text; + + List objectiveValues = _component.GhInOut.GetObjectiveValues(); + if (objectiveValues.Count == 0) { + ghCanvas.EnableUI(); + optimizeRunButton.Enabled = true; return; } - var mesh = (Mesh)DracoCompression.DecompressBase64String(model.Draco); - modelMesh.Append(new GH_Mesh(mesh), new GH_Path(0, model.Number)); + else if (objectiveValues.Count > 1 + && (samplerComboBox.Text == "CMA-ES" || samplerComboBox.Text == "Random" || samplerComboBox.Text == "Grid")) + { + TunnyMessageBox.Show( + "CMA-ES, Random and Grid samplers only support single objective optimization.", + "Tunny", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + ghCanvas.EnableUI(); + optimizeRunButton.Enabled = true; + return; + } + + optimizeBackgroundWorker.RunWorkerAsync(_component); + + optimizeStopButton.Enabled = true; + } - private void FormClosingXButton(object sender, FormClosingEventArgs e) + private void OptimizeStopButton_Click(object sender, EventArgs e) { + optimizeRunButton.Enabled = true; + optimizeStopButton.Enabled = false; + + if (optimizeBackgroundWorker != null) + { + optimizeBackgroundWorker.CancelAsync(); + } + + //Enable GUI var ghCanvas = Owner as GH_DocumentEditor; ghCanvas?.EnableUI(); + + } + + private void OptimizeProgressChangedHandler(object sender, ProgressChangedEventArgs e) + { + var parameters = (IList)e.UserState; + UpdateGrasshopper(parameters); + + optimizeProgressBar.Value = e.ProgressPercentage; + optimizeProgressBar.Update(); } } } \ No newline at end of file diff --git a/Tunny/UI/OptimizationWindow.resx b/Tunny/UI/OptimizationWindow.resx index 1789775b..9b924b14 100644 --- a/Tunny/UI/OptimizationWindow.resx +++ b/Tunny/UI/OptimizationWindow.resx @@ -117,9 +117,12 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + 17, 17 + + 255, 17 + diff --git a/Tunny/Util/GrasshopperInOut.cs b/Tunny/Util/GrasshopperInOut.cs index da93c58d..1d7ee2c7 100644 --- a/Tunny/Util/GrasshopperInOut.cs +++ b/Tunny/Util/GrasshopperInOut.cs @@ -2,6 +2,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Windows.Forms; + +using GalapagosComponents; using Grasshopper.Kernel; using Grasshopper.Kernel.Data; @@ -20,8 +23,9 @@ public class GrasshopperInOut private readonly GH_Document _document; private readonly List _inputGuids; private readonly TunnyComponent _component; + private List _genePool; private IGH_Param _modelMesh; - private List _objectives; + public List Objectives; public List Sliders; public readonly string ComponentFolder; @@ -50,6 +54,7 @@ private bool SetInputs() private bool SetVariables() { Sliders = new List(); + _genePool = new List(); foreach (IGH_Param source in _component.Params.Input[0].Sources) { @@ -62,24 +67,29 @@ private bool SetVariables() return false; } - foreach (Guid guid in _inputGuids) + foreach (IGH_DocumentObject input in _inputGuids.Select(guid => _document.FindObject(guid, true))) { - IGH_DocumentObject input = _document.FindObject(guid, true); - - if (input is GH_NumberSlider slider) + switch (input) { - Sliders.Add(slider); + case GH_NumberSlider slider: + Sliders.Add(slider); + break; + case GalapagosGeneListObject genePool: + _genePool.Add(genePool); + break; } } - SetSliderValues(); + var variables = new List(); + SetInputSliderValues(variables); + SetInputGenePoolValues(variables); + Variables = variables; return true; } - private void SetSliderValues() + private void SetInputSliderValues(ICollection variables) { int i = 0; - var variables = new List(); foreach (GH_NumberSlider slider in Sliders) { @@ -121,10 +131,27 @@ private void SetSliderValues() variables.Add(new Variable(lowerBond, upperBond, isInteger, nickName)); } + } - Variables = variables; + private void SetInputGenePoolValues(ICollection variables) + { + int count = 0; + + foreach (GalapagosGeneListObject genePool in _genePool) + { + bool isInteger = genePool.Decimals == 0; + decimal lowerBond = genePool.Minimum; + decimal upperBond = genePool.Maximum; + + for (int j = 0; j < genePool.Count; j++) + { + string nickName = "genepool" + count++; + variables.Add(new Variable(lowerBond, upperBond, isInteger, nickName)); + } + } } + private bool SetObjectives() { if (_component.Params.Input[1].SourceCount == 0) @@ -133,7 +160,7 @@ private bool SetObjectives() return false; } - _objectives = _component.Params.Input[1].Sources.ToList(); + Objectives = _component.Params.Input[1].Sources.ToList(); return true; } @@ -182,9 +209,23 @@ private bool SetSliderValues(IList parameters) slider.Slider.RaiseEvents = true; } + foreach (GalapagosGeneListObject genePool in _genePool) + { + for (int j = 0; j < genePool.Count; j++) + { + genePool.set_NormalisedValue(j, GetNormalisedGenePoolValue(parameters[i++], genePool)); + genePool.ExpireSolution(false); + } + } + return true; } + private decimal GetNormalisedGenePoolValue(decimal unnormalized, GalapagosGeneListObject genePool) + { + return (unnormalized - genePool.Minimum) / (genePool.Maximum - genePool.Minimum); + } + private void Recalculate() { while (_document.SolutionState != GH_ProcessStep.PreProcess || _document.SolutionDepth != 0) { } @@ -204,15 +245,29 @@ public List GetObjectiveValues() { var values = new List(); - foreach (IGH_Param objective in _objectives) + foreach (IGH_Param objective in Objectives) { - IGH_StructureEnumerator ghEnumerator = objective.VolatileData.AllData(true); + IGH_StructureEnumerator ghEnumerator = objective.VolatileData.AllData(false); + if (ghEnumerator.Count() > 1) + { + TunnyMessageBox.Show( + "Tunny doesn't handle list output.\n Separate each objective if you want multiple objectives", + "Tunny", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + return new List(); + } foreach (IGH_Goo goo in ghEnumerator) { if (goo is GH_Number num) { values.Add(num.Value); } + else if (goo == null) + { + values.Add(double.NaN); + } } } diff --git a/Tunny/Util/ModelResult.cs b/Tunny/Util/ModelResult.cs new file mode 100644 index 00000000..1c992346 --- /dev/null +++ b/Tunny/Util/ModelResult.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Tunny.Util +{ + public class ModelResult + { + public int Number { get; set; } + public string Draco { get; set; } + public Dictionary Variables { get; set; } + public double[] Objectives { get; set; } + } +} \ No newline at end of file