From f3445f3cb9ee9f603aeccdea7f33864376d4c54e Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Wed, 21 Jun 2023 10:38:08 -0700 Subject: [PATCH] Customizable solver config, remove simulator emplace/displace methods --- Benchmark/Program.cs | 4 +-- Craftimizer/SimulatorUtils.cs | 4 +-- Simulator/ActionStates.cs | 2 ++ Simulator/Actions/ActionType.cs | 2 +- Simulator/Effects.cs | 3 ++ Simulator/SimulationState.cs | 29 +++++++++------- Simulator/Simulator.cs | 60 +++++++++------------------------ Solver/Crafty/NodeScores.cs | 3 ++ Solver/Crafty/SimulationNode.cs | 12 ++++--- Solver/Crafty/Simulator.cs | 7 ++-- Solver/Crafty/Solver.cs | 38 ++++++++++----------- Solver/Crafty/SolverConfig.cs | 29 ++++++++++++++++ 12 files changed, 103 insertions(+), 90 deletions(-) create mode 100644 Solver/Crafty/SolverConfig.cs diff --git a/Benchmark/Program.cs b/Benchmark/Program.cs index a788cd8..1360b36 100644 --- a/Benchmark/Program.cs +++ b/Benchmark/Program.cs @@ -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(); diff --git a/Craftimizer/SimulatorUtils.cs b/Craftimizer/SimulatorUtils.cs index 1f31a35..85dd5a3 100644 --- a/Craftimizer/SimulatorUtils.cs +++ b/Craftimizer/SimulatorUtils.cs @@ -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) )); } diff --git a/Simulator/ActionStates.cs b/Simulator/ActionStates.cs index c1785bc..8cd429d 100644 --- a/Simulator/ActionStates.cs +++ b/Simulator/ActionStates.cs @@ -1,7 +1,9 @@ using Craftimizer.Simulator.Actions; +using System.Runtime.InteropServices; namespace Craftimizer.Simulator; +[StructLayout(LayoutKind.Auto)] public struct ActionStates { public byte TouchComboIdx; diff --git a/Simulator/Actions/ActionType.cs b/Simulator/Actions/ActionType.cs index e9dd1df..67a3df7 100644 --- a/Simulator/Actions/ActionType.cs +++ b/Simulator/Actions/ActionType.cs @@ -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() - .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(); } diff --git a/Simulator/Effects.cs b/Simulator/Effects.cs index 320105f..87ec9ac 100644 --- a/Simulator/Effects.cs +++ b/Simulator/Effects.cs @@ -1,5 +1,8 @@ +using System.Runtime.InteropServices; + namespace Craftimizer.Simulator; +[StructLayout(LayoutKind.Auto)] public struct Effects { public byte InnerQuiet; diff --git a/Simulator/SimulationState.cs b/Simulator/SimulationState.cs index c337d7e..138cf8c 100644 --- a/Simulator/SimulationState.cs +++ b/Simulator/SimulationState.cs @@ -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) { diff --git a/Simulator/Simulator.cs b/Simulator/Simulator.cs index e4bd2a1..e6d24f0 100644 --- a/Simulator/Simulator.cs +++ b/Simulator/Simulator.cs @@ -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 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) diff --git a/Solver/Crafty/NodeScores.cs b/Solver/Crafty/NodeScores.cs index b5b868d..a94008e 100644 --- a/Solver/Crafty/NodeScores.cs +++ b/Solver/Crafty/NodeScores.cs @@ -1,5 +1,8 @@ +using System.Runtime.InteropServices; + namespace Craftimizer.Solver.Crafty; +[StructLayout(LayoutKind.Auto)] public struct NodeScores { public float ScoreSum; diff --git a/Solver/Crafty/SimulationNode.cs b/Solver/Crafty/SimulationNode.cs index 0376b90..6b0dcba 100644 --- a/Solver/Crafty/SimulationNode.cs +++ b/Solver/Crafty/SimulationNode.cs @@ -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; } diff --git a/Solver/Crafty/Simulator.cs b/Solver/Crafty/Simulator.cs index 7619683..6765ced 100644 --- a/Solver/Crafty/Simulator.cs +++ b/Solver/Crafty/Simulator.cs @@ -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 diff --git a/Solver/Crafty/Solver.cs b/Solver/Crafty/Solver.cs index e12d51f..d2611c8 100644 --- a/Solver/Crafty/Solver.cs +++ b/Solver/Crafty/Solver.cs @@ -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(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 actions = stackalloc ActionType[MaxStepCount]; + Span 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 Actions, SimulationState State) SearchStepwise(SimulationInput input, Action? actionCallback) + public static (List Actions, SimulationState State) SearchStepwise(SolverConfig config, SimulationInput input, Action? actionCallback) { var state = new SimulationState(input); var actions = new List(); - 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 Actions, SimulationState State) SearchOneshot(SimulationInput input) + public static (List 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); diff --git a/Solver/Crafty/SolverConfig.cs b/Solver/Crafty/SolverConfig.cs new file mode 100644 index 0000000..b4643cf --- /dev/null +++ b/Solver/Crafty/SolverConfig.cs @@ -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; + } +}