even more minor optimizations, use random now
This commit is contained in:
@@ -10,7 +10,7 @@ internal static class Program
|
|||||||
{
|
{
|
||||||
private static void Main()
|
private static void Main()
|
||||||
{
|
{
|
||||||
//TypeLayout.PrintLayout<Solver.Crafty.SimulationNode>(true);
|
//TypeLayout.PrintLayout<Arena<SimulationNode>.Node>(true);
|
||||||
//return;
|
//return;
|
||||||
|
|
||||||
var input = new SimulationInput(
|
var input = new SimulationInput(
|
||||||
@@ -28,17 +28,15 @@ internal static class Program
|
|||||||
QualityDivider = 115,
|
QualityDivider = 115,
|
||||||
ProgressModifier = 80,
|
ProgressModifier = 80,
|
||||||
ProgressDivider = 130,
|
ProgressDivider = 130,
|
||||||
},
|
}
|
||||||
0
|
|
||||||
);
|
);
|
||||||
|
|
||||||
var actions = new List<ActionType>();
|
|
||||||
var s = Stopwatch.StartNew();
|
var s = Stopwatch.StartNew();
|
||||||
if (true)
|
if (true)
|
||||||
(actions, _) = Solver.Crafty.Solver.SearchStepwise(input, actions, a => Console.WriteLine(a));
|
_ = Solver.Crafty.Solver.SearchStepwise(input, a => Console.WriteLine(a));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
(actions, _) = Solver.Crafty.Solver.SearchOneshot(input, actions);
|
(var actions, _) = Solver.Crafty.Solver.SearchOneshot(input);
|
||||||
foreach (var action in actions)
|
foreach (var action in actions)
|
||||||
Console.Write($">{action.IntName()}");
|
Console.Write($">{action.IntName()}");
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace Craftimizer.Simulator.Actions;
|
namespace Craftimizer.Simulator.Actions;
|
||||||
|
|
||||||
public enum ActionType
|
public enum ActionType : byte
|
||||||
{
|
{
|
||||||
AdvancedTouch,
|
AdvancedTouch,
|
||||||
BasicSynthesis,
|
BasicSynthesis,
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ public sealed class SimulationInput
|
|||||||
public int BaseProgressGain { get; }
|
public int BaseProgressGain { get; }
|
||||||
public int BaseQualityGain { get; }
|
public int BaseQualityGain { get; }
|
||||||
|
|
||||||
public SimulationInput(CharacterStats stats, RecipeInfo recipe, int seed)
|
public SimulationInput(CharacterStats stats, RecipeInfo recipe, int? seed = null)
|
||||||
{
|
{
|
||||||
Stats = stats;
|
Stats = stats;
|
||||||
Recipe = recipe;
|
Recipe = recipe;
|
||||||
Random = new Random(seed);
|
Random = seed == null ? new() : new(seed.Value);
|
||||||
|
|
||||||
// https://github.com/NotRanged/NotRanged.github.io/blob/0f4aee074f969fb05aad34feaba605057c08ffd1/app/js/ffxivcraftmodel.js#L88
|
// https://github.com/NotRanged/NotRanged.github.io/blob/0f4aee074f969fb05aad34feaba605057c08ffd1/app/js/ffxivcraftmodel.js#L88
|
||||||
{
|
{
|
||||||
|
|||||||
+27
-34
@@ -3,6 +3,7 @@ using Craftimizer.Simulator.Actions;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Craftimizer.Solver.Crafty;
|
namespace Craftimizer.Solver.Crafty;
|
||||||
|
|
||||||
@@ -12,9 +13,9 @@ public class Solver
|
|||||||
public Simulator Simulator;
|
public Simulator Simulator;
|
||||||
public Arena<SimulationNode> Tree;
|
public Arena<SimulationNode> Tree;
|
||||||
|
|
||||||
//public Random Random => Simulator.Input.Random;
|
public Random Random => Simulator.Input.Random;
|
||||||
|
|
||||||
public const int Iterations = 30000;
|
public const int Iterations = 1000000;
|
||||||
public const float ScoreStorageThreshold = 1f;
|
public const float ScoreStorageThreshold = 1f;
|
||||||
public const float MaxScoreWeightingConstant = 0.1f;
|
public const float MaxScoreWeightingConstant = 0.1f;
|
||||||
public const float ExplorationConstant = 4f;
|
public const float ExplorationConstant = 4f;
|
||||||
@@ -32,7 +33,7 @@ public class Solver
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Solver(SimulationInput input) : this(new(input), false)
|
public Solver(SimulationInput input, bool strict) : this(new SimulationState(input), strict)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ public class Solver
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public (int Index, CompletionState State) ExecuteActions(int startIndex, List<ActionType> actions, bool strict = false)
|
public (int Index, CompletionState State) ExecuteActions(int startIndex, ReadOnlySpan<ActionType> actions, bool strict = false)
|
||||||
{
|
{
|
||||||
var currentIndex = startIndex;
|
var currentIndex = startIndex;
|
||||||
foreach (var action in actions)
|
foreach (var action in actions)
|
||||||
@@ -69,11 +70,11 @@ public class Solver
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static int RustMaxBy(List<int> source, Func<int, float> into)
|
private static int RustMaxBy(ReadOnlySpan<int> source, Func<int, float> into)
|
||||||
{
|
{
|
||||||
var max = 0;
|
var max = 0;
|
||||||
var maxV = into(source[0]);
|
var maxV = into(source[0]);
|
||||||
for (var i = 1; i < source.Count; ++i)
|
for (var i = 1; i < source.Length; ++i)
|
||||||
{
|
{
|
||||||
var nextV = into(source[i]);
|
var nextV = into(source[i]);
|
||||||
if (maxV <= nextV)
|
if (maxV <= nextV)
|
||||||
@@ -97,9 +98,9 @@ public class Solver
|
|||||||
(length + (Vector<float>.Count - 1)) & ~(Vector<float>.Count - 1);
|
(length + (Vector<float>.Count - 1)) & ~(Vector<float>.Count - 1);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private int EvalBestChild(float parentVisits, List<int> children)
|
private int EvalBestChild(float parentVisits, ReadOnlySpan<int> children)
|
||||||
{
|
{
|
||||||
var length = children.Count;
|
var length = children.Length;
|
||||||
|
|
||||||
var C = ExplorationConstant * MathF.Log(parentVisits);
|
var C = ExplorationConstant * MathF.Log(parentVisits);
|
||||||
var w = MaxScoreWeightingConstant;
|
var w = MaxScoreWeightingConstant;
|
||||||
@@ -154,7 +155,7 @@ public class Solver
|
|||||||
}
|
}
|
||||||
|
|
||||||
// select the node with the highest score
|
// select the node with the highest score
|
||||||
selectedIndex = EvalBestChild(selectedNode.State.Data.Scores.Visits, selectedNode.Children);
|
selectedIndex = EvalBestChild(selectedNode.State.Data.Scores.Visits, CollectionsMarshal.AsSpan(selectedNode.Children));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,20 +166,23 @@ public class Solver
|
|||||||
if (initialNode.IsComplete)
|
if (initialNode.IsComplete)
|
||||||
return (initialIndex, initialNode.CompletionState, initialNode.CalculateScore() ?? 0);
|
return (initialIndex, initialNode.CompletionState, initialNode.CalculateScore() ?? 0);
|
||||||
|
|
||||||
var randomAction = initialNode.Data.AvailableActions.ElementAt(0);
|
var randomIdx = Random.Next(initialNode.Data.AvailableActions.Count);
|
||||||
|
var randomAction = initialNode.Data.AvailableActions.ElementAt(randomIdx);
|
||||||
initialNode.Data.AvailableActions.RemoveAction(randomAction);
|
initialNode.Data.AvailableActions.RemoveAction(randomAction);
|
||||||
var expandedState = Execute(initialNode.State, randomAction, true);
|
var expandedState = Execute(initialNode.State, randomAction, true);
|
||||||
var expandedIndex = Tree.Insert(initialIndex, expandedState);
|
var expandedIndex = Tree.Insert(initialIndex, expandedState);
|
||||||
|
|
||||||
// playout to a terminal state
|
// playout to a terminal state
|
||||||
var currentState = Tree.Get(expandedIndex).State;
|
var currentState = Tree.Get(expandedIndex).State;
|
||||||
var actions = new List<ActionType>();
|
byte actionCount = 0;
|
||||||
|
Span<ActionType> actions = stackalloc ActionType[MaxStepCount];
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (currentState.IsComplete)
|
if (currentState.IsComplete)
|
||||||
break;
|
break;
|
||||||
randomAction = currentState.Data.AvailableActions.ElementAt(0);
|
randomIdx = Random.Next(currentState.Data.AvailableActions.Count);
|
||||||
actions.Add(randomAction);
|
randomAction = currentState.Data.AvailableActions.ElementAt(randomIdx);
|
||||||
|
actions[actionCount++] = randomAction;
|
||||||
currentState = Execute(currentState.State, randomAction, true);
|
currentState = Execute(currentState.State, randomAction, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +192,8 @@ public class Solver
|
|||||||
{
|
{
|
||||||
if (score >= ScoreStorageThreshold && score >= Tree.Get(0).State.Data.Scores.MaxScore)
|
if (score >= ScoreStorageThreshold && score >= Tree.Get(0).State.Data.Scores.MaxScore)
|
||||||
{
|
{
|
||||||
(var terminalIndex, _) = ExecuteActions(expandedIndex, actions, true);
|
Console.WriteLine("DONE!");
|
||||||
|
(var terminalIndex, _) = ExecuteActions(expandedIndex, actions[..actionCount], true);
|
||||||
return (terminalIndex, currentState.CompletionState, score);
|
return (terminalIndex, currentState.CompletionState, score);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -227,7 +232,7 @@ public class Solver
|
|||||||
var node = Tree.Get(0);
|
var node = Tree.Get(0);
|
||||||
while (node.Children.Count != 0)
|
while (node.Children.Count != 0)
|
||||||
{
|
{
|
||||||
var next_index = RustMaxBy(node.Children, n => Tree.Get(n).State.Data.Scores.MaxScore);
|
var next_index = RustMaxBy(CollectionsMarshal.AsSpan(node.Children), n => Tree.Get(n).State.Data.Scores.MaxScore);
|
||||||
node = Tree.Get(next_index);
|
node = Tree.Get(next_index);
|
||||||
if (node.State.Action != null)
|
if (node.State.Action != null)
|
||||||
actions.Add(node.State.Action.Value);
|
actions.Add(node.State.Action.Value);
|
||||||
@@ -236,22 +241,11 @@ public class Solver
|
|||||||
return (actions, node.State);
|
return (actions, node.State);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static (SimulationState SimState, CompletionState State) Simulate(SimulationInput input, List<ActionType> actions)
|
public static (List<ActionType> Actions, SimulationState State) SearchStepwise(SimulationInput input, Action<ActionType>? actionCallback)
|
||||||
{
|
{
|
||||||
var solver = new Solver(input);
|
var state = new SimulationState(input);
|
||||||
var (index, result) = solver.ExecuteActions(0, actions);
|
|
||||||
return (solver.Tree.Get(index).State.State, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static (List<ActionType> Actions, SimulationState State) SearchStepwise(SimulationInput input, List<ActionType> actions, Action<ActionType>? actionCallback)
|
|
||||||
{
|
|
||||||
var (state, result) = Simulate(input, actions);
|
|
||||||
if (result != CompletionState.Incomplete)
|
|
||||||
{
|
|
||||||
return (actions, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Debugger.Break();
|
//Debugger.Break();
|
||||||
|
var actions = new List<ActionType>();
|
||||||
var solver = new Solver(state, true);
|
var solver = new Solver(state, true);
|
||||||
while (!solver.Simulator.IsComplete)
|
while (!solver.Simulator.IsComplete)
|
||||||
{
|
{
|
||||||
@@ -272,17 +266,16 @@ public class Solver
|
|||||||
|
|
||||||
solver = new Solver(state, true);
|
solver = new Solver(state, true);
|
||||||
}
|
}
|
||||||
Debugger.Break();
|
//Debugger.Break();
|
||||||
|
|
||||||
return (actions, state);
|
return (actions, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static (List<ActionType> Actions, SimulationState State) SearchOneshot(SimulationInput input, List<ActionType> actions)
|
public static (List<ActionType> Actions, SimulationState State) SearchOneshot(SimulationInput input)
|
||||||
{
|
{
|
||||||
var solver = new Solver(input);
|
var solver = new Solver(input, false);
|
||||||
solver.Search(0);
|
solver.Search(0);
|
||||||
var (solution_actions, solution_node) = solver.Solution();
|
var (solution_actions, solution_node) = solver.Solution();
|
||||||
actions.AddRange(solution_actions);
|
return (solution_actions, solution_node.State);
|
||||||
return (actions, solution_node.State);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user