Customizable solver config, remove simulator emplace/displace methods

This commit is contained in:
Asriel Camora
2023-06-21 10:38:08 -07:00
parent 11b4b7f6d9
commit f3445f3cb9
12 changed files with 103 additions and 90 deletions
+2 -2
View File
@@ -33,10 +33,10 @@ internal static class Program
var s = Stopwatch.StartNew();
if (true)
_ = Solver.Crafty.Solver.SearchStepwise(input, a => Console.WriteLine(a));
_ = Solver.Crafty.Solver.SearchStepwise(new(), input, a => Console.WriteLine(a));
else
{
(var actions, _) = Solver.Crafty.Solver.SearchOneshot(input);
(var actions, _) = Solver.Crafty.Solver.SearchOneshot(new(), input);
foreach (var action in actions)
Console.Write($">{action.IntName()}");
Console.WriteLine();
+2 -2
View File
@@ -34,13 +34,13 @@ internal static class ActionUtils
_ => baseCraftAction
}, null);
}
else if (LuminaSheets.ActionSheet.GetRow(actionId) is Action baseAction)
if (LuminaSheets.ActionSheet.GetRow(actionId) is Action baseAction)
{
return (null,
LuminaSheets.ActionSheet.First(r =>
r.Icon == baseAction.Icon &&
r.ActionCategory.Row == baseAction.ActionCategory.Row &&
r.Name.RawString == baseAction.Name.RawString &&
r.Name.RawString.Equals(baseAction.Name.RawString, StringComparison.Ordinal) &&
(r.ClassJobCategory.Value?.IsClassJob(classJob) ?? false)
));
}
+2
View File
@@ -1,7 +1,9 @@
using Craftimizer.Simulator.Actions;
using System.Runtime.InteropServices;
namespace Craftimizer.Simulator;
[StructLayout(LayoutKind.Auto)]
public struct ActionStates
{
public byte TouchComboIdx;
+1 -1
View File
@@ -46,7 +46,7 @@ public static class ActionUtils
var types = typeof(BaseAction).Assembly.GetTypes()
.Where(t => t.IsAssignableTo(typeof(BaseAction)) && !t.IsAbstract);
Actions = Enum.GetNames<ActionType>()
.Select(a => types.First(t => t.Name == a))
.Select(a => types.First(t => t.Name.Equals(a, StringComparison.Ordinal)))
.Select(t => (Activator.CreateInstance(t) as BaseAction)!)
.ToArray();
}
+3
View File
@@ -1,5 +1,8 @@
using System.Runtime.InteropServices;
namespace Craftimizer.Simulator;
[StructLayout(LayoutKind.Auto)]
public struct Effects
{
public byte InnerQuiet;
+16 -13
View File
@@ -1,18 +1,21 @@
using System.Runtime.InteropServices;
namespace Craftimizer.Simulator;
public readonly struct SimulationState
[StructLayout(LayoutKind.Auto)]
public struct SimulationState
{
public SimulationInput Input { get; init; }
public readonly SimulationInput Input;
public int ActionCount { get; init; }
public int StepCount { get; init; }
public int Progress { get; init; }
public int Quality { get; init; }
public int Durability { get; init; }
public int CP { get; init; }
public Condition Condition { get; init; }
public Effects ActiveEffects { get; init; }
public ActionStates ActionStates { get; init; }
public int ActionCount;
public int StepCount;
public int Progress;
public int Quality;
public int Durability;
public int CP;
public Condition Condition;
public Effects ActiveEffects;
public ActionStates ActionStates;
// https://github.com/ffxiv-teamcraft/simulator/blob/0682dfa76043ff4ccb38832c184d046ceaff0733/src/model/tables.ts#L2
private static readonly int[] HQPercentTable = {
@@ -21,9 +24,9 @@ public readonly struct SimulationState
17, 18, 18, 18, 19, 19, 20, 20, 21, 22, 23, 24, 26, 28, 31, 34, 38, 42, 47, 52, 58, 64, 68, 71,
74, 76, 78, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 94, 96, 98, 100
};
public int HQPercent => HQPercentTable[(int)Math.Clamp((float)Quality / Input.Recipe.MaxQuality * 100, 0, 100)];
public readonly int HQPercent => HQPercentTable[(int)Math.Clamp((float)Quality / Input.Recipe.MaxQuality * 100, 0, 100)];
public bool IsFirstStep => StepCount == 0;
public readonly bool IsFirstStep => StepCount == 0;
public SimulationState(SimulationInput input)
{
+16 -44
View File
@@ -4,18 +4,20 @@ namespace Craftimizer.Simulator;
public class Simulator
{
public SimulationInput Input { get; private set; }
public int ActionCount { get; private set; }
public int StepCount { get; private set; }
public int Progress { get; private set; }
public int Quality { get; private set; }
public int Durability { get; private set; }
public int CP { get; private set; }
public Condition Condition { get; private set; }
public Effects ActiveEffects;
public ActionStates ActionStates;
protected SimulationState State;
public bool IsFirstStep => StepCount == 0;
public SimulationInput Input => State.Input;
public ref int ActionCount => ref State.ActionCount;
public ref int StepCount => ref State.StepCount;
public ref int Progress => ref State.Progress;
public ref int Quality => ref State.Quality;
public ref int Durability => ref State.Durability;
public ref int CP => ref State.CP;
public ref Condition Condition => ref State.Condition;
public ref Effects ActiveEffects => ref State.ActiveEffects;
public ref ActionStates ActionStates => ref State.ActionStates;
public bool IsFirstStep => State.StepCount == 0;
public CompletionState CompletionState
{
@@ -32,45 +34,15 @@ public class Simulator
public IEnumerable<ActionType> AvailableActions => ActionUtils.AvailableActions(this);
#pragma warning disable CS8618 // Emplace sets all the fields already
public Simulator(SimulationState state)
#pragma warning restore CS8618
{
Emplace(state);
State = state;
}
private void Emplace(SimulationState state)
{
Input = state.Input;
ActionCount = state.ActionCount;
StepCount = state.StepCount;
Progress = state.Progress;
Quality = state.Quality;
Durability = state.Durability;
CP = state.CP;
Condition = state.Condition;
ActiveEffects = state.ActiveEffects;
ActionStates = state.ActionStates;
}
private SimulationState Displace() => new()
{
Input = Input,
ActionCount = ActionCount,
StepCount = StepCount,
Progress = Progress,
Quality = Quality,
Durability = Durability,
CP = CP,
Condition = Condition,
ActiveEffects = ActiveEffects,
ActionStates = ActionStates,
};
public (ActionResponse Response, SimulationState NewState) Execute(SimulationState state, ActionType action)
{
Emplace(state);
return (Execute(action), Displace());
State = state;
return (Execute(action), State);
}
private ActionResponse Execute(ActionType action)
+3
View File
@@ -1,5 +1,8 @@
using System.Runtime.InteropServices;
namespace Craftimizer.Solver.Crafty;
[StructLayout(LayoutKind.Auto)]
public struct NodeScores
{
public float ScoreSum;
+7 -5
View File
@@ -1,8 +1,10 @@
using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions;
using System.Runtime.InteropServices;
namespace Craftimizer.Solver.Crafty;
[StructLayout(LayoutKind.Auto)]
public struct SimulationNode
{
public readonly SimulationState State;
@@ -12,9 +14,9 @@ public struct SimulationNode
public ActionSet AvailableActions;
public NodeScores Scores;
public CompletionState CompletionState => GetCompletionState(SimulationCompletionState, AvailableActions);
public readonly CompletionState CompletionState => GetCompletionState(SimulationCompletionState, AvailableActions);
public bool IsComplete => CompletionState != CompletionState.Incomplete;
public readonly bool IsComplete => CompletionState != CompletionState.Incomplete;
public SimulationNode(SimulationState state, ActionType? action, CompletionState completionState, ActionSet actions)
{
@@ -29,9 +31,9 @@ public struct SimulationNode
CompletionState.NoMoreActions :
simCompletionState;
public float? CalculateScore() => CalculateScoreForState(State, SimulationCompletionState);
public readonly float? CalculateScore(int maxStepCount) => CalculateScoreForState(State, SimulationCompletionState, maxStepCount);
public static float? CalculateScoreForState(SimulationState state, CompletionState completionState)
public static float? CalculateScoreForState(SimulationState state, CompletionState completionState, int maxStepCount)
{
if (completionState != CompletionState.ProgressComplete)
return null;
@@ -70,7 +72,7 @@ public struct SimulationNode
);
var fewerStepsScore =
fewerStepsBonus * (1f - ((float)(state.ActionCount + 1) / Solver.MaxStepCount));
fewerStepsBonus * (1f - ((float)(state.ActionCount + 1) / maxStepCount));
return progressScore + qualityScore + durabilityScore + cpScore + fewerStepsScore;
}
+5 -2
View File
@@ -6,14 +6,17 @@ namespace Craftimizer.Solver.Crafty;
public class Simulator : Sim
{
private readonly int maxStepCount;
public new CompletionState CompletionState =>
(ActionCount + 1) >= Solver.MaxStepCount ?
(ActionCount + 1) >= maxStepCount ?
CompletionState.MaxActionCountReached :
(CompletionState)base.CompletionState;
public override bool IsComplete => CompletionState != CompletionState.Incomplete;
public Simulator(SimulationState state) : base(state)
public Simulator(SimulationState state, int maxStepCount) : base(state)
{
this.maxStepCount = maxStepCount;
}
// Disable randomization
+17 -21
View File
@@ -10,20 +10,16 @@ namespace Craftimizer.Solver.Crafty;
// https://github.com/alostsock/crafty/blob/cffbd0cad8bab3cef9f52a3e3d5da4f5e3781842/crafty/src/simulator.rs
public class Solver
{
public SolverConfig Config;
public Simulator Simulator;
public Node RootNode;
// public Random Random => Simulator.Input.Random;
public const int Iterations = 30000;
public const float ScoreStorageThreshold = 1f;
public const float MaxScoreWeightingConstant = 0.1f;
public const float ExplorationConstant = 4f;
public const int MaxStepCount = 25;
public Solver(SimulationState state, bool strict)
public Solver(SolverConfig config, SimulationState state, bool strict)
{
Simulator = new(state);
Config = config;
Simulator = new(state, config.MaxStepCount);
RootNode = new(new(
state,
null,
@@ -32,7 +28,7 @@ public class Solver
));
}
public Solver(SimulationInput input, bool strict) : this(new SimulationState(input), strict)
public Solver(SolverConfig config, SimulationInput input, bool strict) : this(config, new SimulationState(input), strict)
{
}
@@ -104,8 +100,8 @@ public class Solver
{
var length = children.Length;
var C = ExplorationConstant * MathF.Log(parentVisits);
var w = MaxScoreWeightingConstant;
var C = Config.ExplorationConstant * MathF.Log(parentVisits);
var w = Config.MaxScoreWeightingConstant;
var W = 1f - w;
var CVector = new Vector<float>(C);
@@ -162,7 +158,7 @@ public class Solver
ref var initialState = ref initialNode.State;
// expand once
if (initialState.IsComplete)
return (initialNode, initialState.CompletionState, initialState.CalculateScore() ?? 0);
return (initialNode, initialState.CompletionState, initialState.CalculateScore(Config.MaxStepCount) ?? 0);
var randomAction = initialState.AvailableActions.First();
initialState.AvailableActions.RemoveAction(randomAction);
@@ -174,7 +170,7 @@ public class Solver
var currentActions = expandedNode.State.AvailableActions;
byte actionCount = 0;
Span<ActionType> actions = stackalloc ActionType[MaxStepCount];
Span<ActionType> actions = stackalloc ActionType[Config.MaxStepCount];
while (true)
{
if (SimulationNode.GetCompletionState(currentCompletionState, currentActions) != CompletionState.Incomplete)
@@ -186,10 +182,10 @@ public class Solver
// store the result if a max score was reached
currentCompletionState = SimulationNode.GetCompletionState(currentCompletionState, currentActions);
var score = SimulationNode.CalculateScoreForState(currentState, currentCompletionState) ?? 0;
var score = SimulationNode.CalculateScoreForState(currentState, currentCompletionState, Config.MaxStepCount) ?? 0;
if (currentCompletionState == CompletionState.ProgressComplete)
{
if (score >= ScoreStorageThreshold && score >= RootNode.State.Scores.MaxScore)
if (score >= Config.ScoreStorageThreshold && score >= RootNode.State.Scores.MaxScore)
{
(var terminalNode, _) = ExecuteActions(expandedNode, actions[..actionCount], true);
return (terminalNode, currentCompletionState, score);
@@ -213,7 +209,7 @@ public class Solver
public void Search(Node startNode)
{
for (var i = 0; i < Iterations; i++)
for (var i = 0; i < Config.Iterations; i++)
{
var selectedNode = Select(startNode);
var (endNode, _, score) = ExpandAndRollout(selectedNode);
@@ -236,11 +232,11 @@ public class Solver
return (actions, node.State);
}
public static (List<ActionType> Actions, SimulationState State) SearchStepwise(SimulationInput input, Action<ActionType>? actionCallback)
public static (List<ActionType> Actions, SimulationState State) SearchStepwise(SolverConfig config, SimulationInput input, Action<ActionType>? actionCallback)
{
var state = new SimulationState(input);
var actions = new List<ActionType>();
var solver = new Solver(state, true);
var solver = new Solver(config, state, true);
while (!solver.Simulator.IsComplete)
{
solver.Search(solver.RootNode);
@@ -258,15 +254,15 @@ public class Solver
actionCallback?.Invoke(chosen_action);
solver = new Solver(state, true);
solver = new Solver(config, state, true);
}
return (actions, state);
}
public static (List<ActionType> Actions, SimulationState State) SearchOneshot(SimulationInput input)
public static (List<ActionType> Actions, SimulationState State) SearchOneshot(SolverConfig config, SimulationInput input)
{
var solver = new Solver(input, false);
var solver = new Solver(config, input, false);
solver.Search(solver.RootNode);
var (solution_actions, solution_node) = solver.Solution();
return (solution_actions, solution_node.State);
+29
View File
@@ -0,0 +1,29 @@
using System.Runtime.InteropServices;
namespace Craftimizer.Solver.Crafty;
[StructLayout(LayoutKind.Auto)]
public readonly struct SolverConfig
{
public readonly int Iterations;
public readonly float ScoreStorageThreshold;
public readonly float MaxScoreWeightingConstant;
public readonly float ExplorationConstant;
public readonly int MaxStepCount;
public SolverConfig() : this(30000, 1f, 0.1f, 4, 25) { }
public SolverConfig(
int iterations = 30000,
float scoreStorageThreshold = 1f,
float maxScoreWeightingConstant = 0.1f,
float explorationConstant = 4f,
int maxStepCount = 25)
{
Iterations = iterations;
ScoreStorageThreshold = scoreStorageThreshold;
MaxScoreWeightingConstant = maxScoreWeightingConstant;
ExplorationConstant = explorationConstant;
MaxStepCount = maxStepCount;
}
}