Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support categorical optimization #233

Merged
merged 11 commits into from
Feb 1, 2024
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,33 @@ 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.

## [v0.11.0] -yyyy-mm-dd

### Added

- Categorical optimization.
- log output.

### Changed

for changes in existing functionality.

### Deprecated

for soon-to-be removed features.

### Removed

for now removed features.

### Fixed

for any bug fixes.

### Security

in case of vulnerabilities.

## [v0.10.0] -2024-01-27

### Changed
Expand Down
Binary file added Samples/Grasshopper/categorical_optimization.gh
Binary file not shown.
7 changes: 4 additions & 3 deletions Tunny/Component/Optimizer/FishingComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,10 @@ private void CheckVariablesInput(IEnumerable<Guid> inputGuids)
{
switch (docObject)
{
case GH_NumberSlider slider:
case GalapagosGeneListObject genePool:
case Param_FishEgg fishEgg:
case GH_ValueList _:
case GH_NumberSlider _:
case GalapagosGeneListObject _:
case Param_FishEgg _:
break;
default:
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"{docObject} input is not a valid variable.");
Expand Down
58 changes: 45 additions & 13 deletions Tunny/Component/Util/ConstructFishEgg.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

using Grasshopper.Kernel;

using Serilog;

using Tunny.Component.Params;
using Tunny.Input;
using Tunny.Type;
Expand Down Expand Up @@ -58,7 +60,7 @@ protected override void SolveInstance(IGH_DataAccess DA)
private void LayFishEgg()
{
var ghIO = new GrasshopperInOut(this, getVariableOnly: true);
List<Variable> variables = ghIO.Variables;
List<VariableBase> variables = ghIO.Variables;

bool isContainedVariableSets = false;
if (_fishEggs.Count > 0)
Expand All @@ -72,31 +74,61 @@ private void LayFishEgg()
}
}

private bool CheckVariableSetsIsContained(IEnumerable<Variable> variables)
private bool CheckVariableSetsIsContained(IEnumerable<VariableBase> variables)
{
int sameValueCount = 0;
foreach (Variable variable in variables)
foreach (VariableBase variable in variables)
{
if (_fishEggs.TryGetValue(variable.NickName, out FishEgg egg) && egg.Values.Contains(variable.Value))
string name = variable.NickName;
switch (variable)
{
sameValueCount++;
case NumberVariable number:
if (_fishEggs.TryGetValue(name, out FishEgg eggNum) && eggNum.Values.Contains(number.Value))
{
sameValueCount++;
}
break;
case CategoricalVariable category:
if (_fishEggs.TryGetValue(name, out FishEgg eggCat) && eggCat.Category == category.SelectedItem)
{
sameValueCount++;
}
continue;
}
}
bool isContainVariableSets = sameValueCount == _fishEggs.Count;
return isContainVariableSets;
}

private void AddVariablesToFishEgg(IEnumerable<Variable> variables)
private void AddVariablesToFishEgg(IEnumerable<VariableBase> variables)
{
foreach (Variable variable in variables)
foreach (VariableBase variable in variables)
{
if (_fishEggs.TryGetValue(variable.NickName, out FishEgg egg))
{
egg.Values.Add(variable.Value);
}
else
string name = variable.NickName;
switch (variable)
{
_fishEggs.Add(variable.NickName, new FishEgg(variable));
case NumberVariable number:
if (_fishEggs.TryGetValue(name, out FishEgg eggNum))
{
eggNum.Values.Add(number.Value);
}
else
{
_fishEggs.Add(name, new FishEgg(number));
}
break;
case CategoricalVariable category:
if (_fishEggs.TryGetValue(name, out FishEgg eggCat))
{
string message = $"This Categorical variable {name} is already contained in fish egg.";
Log.Error(message);
throw new ArgumentException(message);
}
else
{
_fishEggs.Add(name, new FishEgg(category));
}
break;
}
}
}
Expand Down
28 changes: 19 additions & 9 deletions Tunny/Component/Util/DeconstructFish.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ protected override void RegisterInputParams(GH_InputParamManager pManager)

protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddNumberParameter("Variables", "Vars", "Variables", GH_ParamAccess.tree);
pManager.AddNumberParameter("NumberVariables", "NumVars", "Number Variables", GH_ParamAccess.tree);
pManager.AddTextParameter("TextVariables", "TxtVars", "Text Variables", GH_ParamAccess.tree);
pManager.AddNumberParameter("Objectives", "Objs", "Objectives", GH_ParamAccess.tree);
pManager.AddParameter(new Param_FishAttribute(), "Attributes", "Attrs", "Attributes", GH_ParamAccess.tree);
}
Expand All @@ -42,29 +43,38 @@ protected override void SolveInstance(IGH_DataAccess DA)

var fishes = fishObjects.Select(x => (GH_Fish)x).ToList();

var variables = new GH_Structure<GH_Number>();
var numberVariables = new GH_Structure<GH_Number>();
var textVariables = new GH_Structure<GH_String>();
var objectives = new GH_Structure<GH_Number>();
var attributes = new GH_Structure<GH_FishAttribute>();

foreach (GH_Fish fish in fishes)
{
Fish value = fish.Value;
var path = new GH_Path(0, value.ModelNumber);
SetVariables(variables, value, path);
SetVariables(numberVariables, textVariables, value, path);
SetObjectives(objectives, value, path);
SetAttributes(attributes, value, path);
}

DA.SetDataTree(0, variables);
DA.SetDataTree(1, objectives);
DA.SetDataTree(2, attributes);
DA.SetDataTree(0, numberVariables);
DA.SetDataTree(1, textVariables);
DA.SetDataTree(2, objectives);
DA.SetDataTree(3, attributes);
}

private static void SetVariables(GH_Structure<GH_Number> variables, Fish value, GH_Path path)
private static void SetVariables(GH_Structure<GH_Number> number, GH_Structure<GH_String> text, Fish value, GH_Path path)
{
foreach (KeyValuePair<string, double> variable in value.Variables)
foreach (KeyValuePair<string, object> variable in value.Variables)
{
variables.Append(new GH_Number(variable.Value), path);
if (variable.Value is double v)
{
number.Append(new GH_Number(v), path);
}
else
{
text.Append(new GH_String(variable.Value.ToString()), path);
}
}
}

Expand Down
15 changes: 7 additions & 8 deletions Tunny/Handler/OptimizeLoop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,14 @@ internal static void RunMultiple(object sender, DoWorkEventArgs e)
s_component = e.Argument as FishingComponent;
s_component?.GhInOutInstantiate();

double[] result = RunOptimizationLoop(s_worker);
if (result == null || double.IsNaN(result[0]))
Parameter[] optimalParams = RunOptimizationLoop(s_worker);
if (optimalParams == null || optimalParams.Length == 0)
{
return;
}
var decimalResults = result.Select(Convert.ToDecimal).ToList();
var pState = new ProgressState
{
Values = decimalResults
Parameter = optimalParams
};

if (s_component != null)
Expand All @@ -48,22 +47,22 @@ internal static void RunMultiple(object sender, DoWorkEventArgs e)
s_worker?.CancelAsync();
}

private static double[] RunOptimizationLoop(BackgroundWorker worker)
private static Parameter[] RunOptimizationLoop(BackgroundWorker worker)
{
List<Variable> variables = s_component.GhInOut.Variables;
List<VariableBase> variables = s_component.GhInOut.Variables;
Objective objectives = s_component.GhInOut.Objectives;
Dictionary<string, FishEgg> enqueueItems = s_component.GhInOut.EnqueueItems;
bool hasConstraint = s_component.GhInOut.HasConstraint;

if (worker.CancellationPending)
{
return new[] { double.NaN };
return Array.Empty<Parameter>();
}

var optunaSolver = new Solver.Optuna(s_component.GhInOut.ComponentFolder, Settings, hasConstraint);
bool solverStarted = optunaSolver.RunSolver(variables, objectives, enqueueItems, EvaluateFunction);

return solverStarted ? optunaSolver.XOpt : new[] { double.NaN };
return solverStarted ? optunaSolver.OptimalParameters : Array.Empty<Parameter>();
}

private static TrialGrasshopperItems EvaluateFunction(ProgressState pState, int progress)
Expand Down
2 changes: 1 addition & 1 deletion Tunny/Handler/OutputLoop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ private static void SetResultToFish(List<Fish> fishes, ModelResult model, IEnume
});
}

private static Dictionary<string, double> SetVariables(ModelResult model, IEnumerable<string> nickNames)
private static Dictionary<string, object> SetVariables(ModelResult model, IEnumerable<string> nickNames)
{
return nickNames.SelectMany(name => model.Variables.Where(obj => obj.Key == name))
.ToDictionary(variable => variable.Key, variable => variable.Value);
Expand Down
4 changes: 3 additions & 1 deletion Tunny/Handler/ProgressState.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System;
using System.Collections.Generic;

using Tunny.Input;

namespace Tunny.Handler
{
public class ProgressState
{
public IList<decimal> Values { get; set; }
public IList<Parameter> Parameter { get; set; }
public int TrialNumber { get; set; }
public int ObjectiveNum { get; set; }
public double[][] BestValues { get; set; }
Expand Down
18 changes: 18 additions & 0 deletions Tunny/Input/CategoricalVariable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;

namespace Tunny.Input
{
[Serializable]
public class CategoricalVariable : VariableBase
{
public string[] Categories { get; }
public string SelectedItem { get; }

public CategoricalVariable(string[] categories, string selectedItem, string nickName, Guid id)
: base(nickName, id)
{
Categories = categories;
SelectedItem = selectedItem;
}
}
}
9 changes: 3 additions & 6 deletions Tunny/Input/Variables.cs → Tunny/Input/NumberVariable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,24 @@
namespace Tunny.Input
{
[Serializable]
public class Variable
public class NumberVariable : VariableBase
{
public double LowerBond { get; }
public double UpperBond { get; }
public bool IsInteger { get; }
public string NickName { get; }
public double Epsilon { get; }
public double Value { get; }
public Guid InstanceId { get; }

public Variable(
public NumberVariable(
double lowerBond, double upperBond, bool isInteger, string nickName, double epsilon,
double value, Guid id)
: base(nickName, id)
{
LowerBond = lowerBond;
UpperBond = upperBond;
IsInteger = isInteger;
NickName = nickName;
Epsilon = epsilon;
Value = value;
InstanceId = id;
}
}
}
34 changes: 34 additions & 0 deletions Tunny/Input/Paramter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Globalization;

namespace Tunny.Input
{
public class Parameter
{
public string Category { get; }
public double Number { get; }
public bool HasNumber => !HasCategory;
public bool HasCategory => !string.IsNullOrEmpty(Category);

public Parameter(double number)
{
Number = number;
}

public Parameter(string category)
{
Category = category;
}

public override string ToString()
{
if (HasCategory)
{
return Category;
}
else
{
return Number.ToString(CultureInfo.InvariantCulture);
}
}
}
}
16 changes: 16 additions & 0 deletions Tunny/Input/VariableBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;

namespace Tunny.Input
{
public class VariableBase
{
public string NickName { get; }
public Guid InstanceId { get; }

public VariableBase(string nickName, Guid id)
{
NickName = nickName;
InstanceId = id;
}
}
}
2 changes: 1 addition & 1 deletion Tunny/PostProcess/ModelResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public class ModelResult
{
public int Number { get; set; }
public double[] Objectives { get; set; }
public Dictionary<string, double> Variables { get; set; }
public Dictionary<string, object> Variables { get; set; }
public Dictionary<string, List<string>> Attributes { get; set; }
}
}
Loading
Loading