From c8d3161eb1dadb1f95b419eccaaa245e33df7d84 Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Thu, 29 Feb 2024 02:47:59 -0800 Subject: [PATCH] Get rid of ActionPool, use ulong for ActionSet --- Solver/ActionPool.cs | 97 ---------------------------- Solver/ActionSet.cs | 48 ++++++++------ Solver/MCTS.cs | 8 +-- Solver/MCTSConfig.cs | 3 +- Solver/Simulator.cs | 12 ++-- Solver/SolverConfig.cs | 38 ++++++++++- Test/Solver/ActionSet.cs | 132 +++++++++++---------------------------- 7 files changed, 112 insertions(+), 226 deletions(-) delete mode 100644 Solver/ActionPool.cs diff --git a/Solver/ActionPool.cs b/Solver/ActionPool.cs deleted file mode 100644 index c7682a2..0000000 --- a/Solver/ActionPool.cs +++ /dev/null @@ -1,97 +0,0 @@ -using Craftimizer.Simulator.Actions; -using System.Diagnostics.Contracts; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace Craftimizer.Solver; - -[StructLayout(LayoutKind.Auto)] -public readonly struct ActionPool -{ - public static ActionPool Default { get; } = new(new[] - { - ActionType.StandardTouchCombo, - ActionType.AdvancedTouchCombo, - ActionType.FocusedTouchCombo, - ActionType.FocusedSynthesisCombo, - ActionType.TrainedFinesse, - ActionType.PrudentSynthesis, - ActionType.Groundwork, - ActionType.AdvancedTouch, - ActionType.CarefulSynthesis, - ActionType.TrainedEye, - ActionType.DelicateSynthesis, - ActionType.PreparatoryTouch, - ActionType.Reflect, - ActionType.PrudentTouch, - ActionType.Manipulation, - ActionType.MuscleMemory, - ActionType.ByregotsBlessing, - ActionType.WasteNot2, - ActionType.BasicSynthesis, - ActionType.Innovation, - ActionType.GreatStrides, - ActionType.StandardTouch, - ActionType.Veneration, - ActionType.WasteNot, - ActionType.MastersMend, - ActionType.BasicTouch, - }); - - public const int MaskSize = 32; - - // Bitmask of accepted actions - private readonly ulong acceptedActions; - - internal ActionType[] AcceptedActions => GetActions(); - internal int Count => BitOperations.PopCount(acceptedActions); - - public ActionPool(ReadOnlySpan actions) - { - acceptedActions = 0; - foreach (var action in actions) - acceptedActions |= 1ul << ((byte)action); - - if (Count > MaskSize) - throw new ArgumentOutOfRangeException(nameof(actions), actions.Length, $"ActionPool only supports up to {MaskSize} actions"); - } - - private ActionType[] GetActions() - { - var ret = new ActionType[Count]; - var i = 0; - foreach (var v in (byte[])Enum.GetValuesAsUnderlyingType()) - { - if ((acceptedActions & (1ul << v)) != 0) - ret[i++] = (ActionType)v; - } - return ret; - } - - [Pure] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal int FromAction(ActionType action) - { - if ((acceptedActions & (1ul << (byte)action)) == 0) - throw new ArgumentOutOfRangeException(nameof(action), action, "Action is not accepted by this pool."); - - // Get number of 1s before action - return BitOperations.PopCount(acceptedActions & ((1ul << (byte)action) - 1)); - } - - [Pure] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ActionType ToAction(int index) - { - if (index < 0 || index >= Count) - throw new ArgumentOutOfRangeException(nameof(index), index, $"Index {index} is out of range for this pool."); - - // Get index of (index+1)th 1 in set - return (ActionType)Intrinsics.NthBitSet(acceptedActions, index); - } - - [Pure] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal uint ToMask(ActionType action) => 1u << (FromAction(action) + 1); -} diff --git a/Solver/ActionSet.cs b/Solver/ActionSet.cs index 989c828..860975d 100644 --- a/Solver/ActionSet.cs +++ b/Solver/ActionSet.cs @@ -7,13 +7,25 @@ namespace Craftimizer.Solver; public struct ActionSet { - internal uint bits; + internal ulong bits; + + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int FromAction(ActionType action) => (byte)action; + + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ActionType ToAction(int index) => (ActionType)index; + + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ulong ToMask(ActionType action) => 1ul << FromAction(action); // Return true if action was newly added and not there before. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool AddAction(in ActionPool pool, ActionType action) + public bool AddAction(ActionType action) { - var mask = pool.ToMask(action); + var mask = ToMask(action); var old = bits; bits |= mask; return (old & mask) == 0; @@ -21,9 +33,9 @@ public struct ActionSet // Return true if action was newly removed and not already gone. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool RemoveAction(in ActionPool pool, ActionType action) + public bool RemoveAction(ActionType action) { - var mask = pool.ToMask(action); + var mask = ToMask(action); var old = bits; bits &= ~mask; return (old & mask) != 0; @@ -31,10 +43,10 @@ public struct ActionSet [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool HasAction(in ActionPool pool, ActionType action) => (bits & pool.ToMask(action)) != 0; + public readonly bool HasAction(ActionType action) => (bits & ToMask(action)) != 0; [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ActionType ElementAt(in ActionPool pool, int index) => pool.ToAction(Intrinsics.NthBitSet(bits, index) - 1); + public readonly ActionType ElementAt(int index) => ToAction(Intrinsics.NthBitSet(bits, index)); [Pure] public readonly int Count => BitOperations.PopCount(bits); @@ -43,38 +55,38 @@ public struct ActionSet public readonly bool IsEmpty => bits == 0; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ActionType SelectRandom(in ActionPool pool, Random random) + public readonly ActionType SelectRandom(Random random) { #if IS_DETERMINISTIC - return First(in pool); + return First(); #else - return ElementAt(in pool, random.Next(Count)); + return ElementAt(random.Next(Count)); #endif } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ActionType PopRandom(in ActionPool pool, Random random) + public ActionType PopRandom(Random random) { #if IS_DETERMINISTIC - return PopFirst(in pool); + return PopFirst(); #else - var action = ElementAt(in pool, random.Next(Count)); - RemoveAction(in pool, action); + var action = ElementAt(random.Next(Count)); + RemoveAction(action); return action; #endif } #if IS_DETERMINISTIC [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ActionType PopFirst(in pool) + private ActionType PopFirst() { - var action = First(in pool); - RemoveAction(in pool, action); + var action = First(); + RemoveAction(action); return action; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - private readonly ActionType First(in pool) => ElementAt(in pool, 0); + private readonly ActionType First() => ElementAt(0); #endif } diff --git a/Solver/MCTS.cs b/Solver/MCTS.cs index 712aad8..afcd89e 100644 --- a/Solver/MCTS.cs +++ b/Solver/MCTS.cs @@ -52,9 +52,9 @@ public sealed class MCTS if (state.IsComplete) return startNode; - if (!state.AvailableActions.HasAction(in simulator.Pool, action)) + if (!state.AvailableActions.HasAction(action)) return startNode; - state.AvailableActions.RemoveAction(in simulator.Pool, action); + state.AvailableActions.RemoveAction(action); startNode = startNode.Add(Execute(simulator, state.State, action, strict)); } @@ -184,7 +184,7 @@ public sealed class MCTS if (initialState.IsComplete) return (initialNode, initialState.CalculateScore(config) ?? 0); - var poppedAction = initialState.AvailableActions.PopRandom(in simulator.Pool, random); + var poppedAction = initialState.AvailableActions.PopRandom(random); var expandedNode = initialNode.Add(Execute(simulator, initialState.State, poppedAction, true)); // playout to a terminal state @@ -198,7 +198,7 @@ public sealed class MCTS while (SimulationNode.GetCompletionState(currentCompletionState, currentActions) == CompletionState.Incomplete && actionCount < actions.Length) { - var nextAction = currentActions.SelectRandom(in simulator.Pool, random); + var nextAction = currentActions.SelectRandom(random); actions[actionCount++] = nextAction; (_, currentState) = simulator.Execute(currentState, nextAction); currentCompletionState = simulator.CompletionState; diff --git a/Solver/MCTSConfig.cs b/Solver/MCTSConfig.cs index 0451981..8f7f091 100644 --- a/Solver/MCTSConfig.cs +++ b/Solver/MCTSConfig.cs @@ -1,3 +1,4 @@ +using Craftimizer.Simulator.Actions; using System.Runtime.InteropServices; namespace Craftimizer.Solver; @@ -21,7 +22,7 @@ public readonly record struct MCTSConfig public float ScoreCP { get; init; } public float ScoreSteps { get; init; } - public ActionPool ActionPool { get; init; } + public ActionType[] ActionPool { get; init; } public MCTSConfig(in SolverConfig config) { diff --git a/Solver/Simulator.cs b/Solver/Simulator.cs index 45fc9a4..eb2176e 100644 --- a/Solver/Simulator.cs +++ b/Solver/Simulator.cs @@ -7,8 +7,7 @@ namespace Craftimizer.Solver; internal sealed class Simulator : SimulatorNoRandom { - public readonly ActionPool Pool; - private readonly ActionType[] poolActions; + private readonly ActionType[] actionPool; private readonly int maxStepCount; public override CompletionState CompletionState @@ -22,10 +21,9 @@ internal sealed class Simulator : SimulatorNoRandom } } - public Simulator(in ActionPool pool, int maxStepCount) + public Simulator(ActionType[] actionPool, int maxStepCount) { - Pool = pool; - poolActions = Pool.AcceptedActions; + this.actionPool = actionPool; this.maxStepCount = maxStepCount; } @@ -137,9 +135,9 @@ internal sealed class Simulator : SimulatorNoRandom return new(); var ret = new ActionSet(); - foreach (var action in poolActions) + foreach (var action in actionPool) if (CanUseAction(action, strict)) - ret.AddAction(in Pool, action); + ret.AddAction(action); return ret; } diff --git a/Solver/SolverConfig.cs b/Solver/SolverConfig.cs index 1d03b1d..f6c9830 100644 --- a/Solver/SolverConfig.cs +++ b/Solver/SolverConfig.cs @@ -1,3 +1,4 @@ +using Craftimizer.Simulator.Actions; using System.Runtime.InteropServices; namespace Craftimizer.Solver; @@ -31,7 +32,7 @@ public readonly record struct SolverConfig public float ScoreCP { get; init; } public float ScoreSteps { get; init; } - public ActionPool ActionPool { get; init; } + public ActionType[] ActionPool { get; init; } public SolverAlgorithm Algorithm { get; init; } public SolverConfig() @@ -55,10 +56,43 @@ public readonly record struct SolverConfig ScoreCP = .05f; ScoreSteps = .05f; - ActionPool = ActionPool.Default; + ActionPool = DefaultActionPool; Algorithm = SolverAlgorithm.StepwiseFurcated; } + public static ActionType[] OptimizeActionPool(IEnumerable actions) => + actions.Order().ToArray(); + + public static readonly ActionType[] DefaultActionPool = OptimizeActionPool(new[] + { + ActionType.StandardTouchCombo, + ActionType.AdvancedTouchCombo, + ActionType.FocusedTouchCombo, + ActionType.FocusedSynthesisCombo, + ActionType.TrainedFinesse, + ActionType.PrudentSynthesis, + ActionType.Groundwork, + ActionType.AdvancedTouch, + ActionType.CarefulSynthesis, + ActionType.TrainedEye, + ActionType.DelicateSynthesis, + ActionType.PreparatoryTouch, + ActionType.Reflect, + ActionType.PrudentTouch, + ActionType.Manipulation, + ActionType.MuscleMemory, + ActionType.ByregotsBlessing, + ActionType.WasteNot2, + ActionType.BasicSynthesis, + ActionType.Innovation, + ActionType.GreatStrides, + ActionType.StandardTouch, + ActionType.Veneration, + ActionType.WasteNot, + ActionType.MastersMend, + ActionType.BasicTouch, + }); + public static readonly SolverConfig SimulatorDefault = new SolverConfig() with { diff --git a/Test/Solver/ActionSet.cs b/Test/Solver/ActionSet.cs index f2d6e77..ec170fa 100644 --- a/Test/Solver/ActionSet.cs +++ b/Test/Solver/ActionSet.cs @@ -1,70 +1,8 @@ -using System.Runtime.CompilerServices; - namespace Craftimizer.Test.Solver; [TestClass] public class ActionSetTests { - private readonly ActionPool pool = ActionPool.Default; - - [TestMethod] - public void TestActionPoolSize() - { - Assert.AreEqual(ActionPool.MaskSize, Unsafe.SizeOf() * 8); - } - - [TestMethod] - public void TestActionPoolConstructor() - { - CollectionAssert.AreEquivalent(new ActionType[] - { - ActionType.StandardTouchCombo, - ActionType.AdvancedTouchCombo, - ActionType.FocusedTouchCombo, - ActionType.FocusedSynthesisCombo, - ActionType.TrainedFinesse, - ActionType.PrudentSynthesis, - ActionType.Groundwork, - ActionType.AdvancedTouch, - ActionType.CarefulSynthesis, - ActionType.TrainedEye, - ActionType.DelicateSynthesis, - ActionType.PreparatoryTouch, - ActionType.Reflect, - ActionType.PrudentTouch, - ActionType.Manipulation, - ActionType.MuscleMemory, - ActionType.ByregotsBlessing, - ActionType.WasteNot2, - ActionType.BasicSynthesis, - ActionType.Innovation, - ActionType.GreatStrides, - ActionType.StandardTouch, - ActionType.Veneration, - ActionType.WasteNot, - ActionType.MastersMend, - ActionType.BasicTouch, - }, pool.AcceptedActions); - } - - [TestMethod] - public void TestAcceptedActions() - { - foreach (var i in Enum.GetValues()) - { - int idx; - try - { - idx = pool.FromAction(i); - } - catch (ArgumentOutOfRangeException) - { - continue; - } - Assert.AreEqual(i, pool.ToAction(idx)); - } - } - [TestMethod] public void TestSize() { @@ -72,14 +10,14 @@ public class ActionSetTests Assert.IsTrue(set.IsEmpty); Assert.AreEqual(0, set.Count); - set.AddAction(in pool, ActionType.BasicSynthesis); - set.AddAction(in pool, ActionType.WasteNot2); + set.AddAction(ActionType.BasicSynthesis); + set.AddAction(ActionType.WasteNot2); Assert.AreEqual(2, set.Count); Assert.IsFalse(set.IsEmpty); - set.RemoveAction(in pool, ActionType.BasicSynthesis); - set.RemoveAction(in pool, ActionType.WasteNot2); + set.RemoveAction(ActionType.BasicSynthesis); + set.RemoveAction(ActionType.WasteNot2); Assert.IsTrue(set.IsEmpty); Assert.AreEqual(0, set.Count); @@ -90,17 +28,17 @@ public class ActionSetTests { var set = new ActionSet(); - Assert.IsTrue(set.AddAction(in pool, ActionType.BasicSynthesis)); - Assert.IsFalse(set.AddAction(in pool, ActionType.BasicSynthesis)); + Assert.IsTrue(set.AddAction(ActionType.BasicSynthesis)); + Assert.IsFalse(set.AddAction(ActionType.BasicSynthesis)); - Assert.IsTrue(set.RemoveAction(in pool, ActionType.BasicSynthesis)); - Assert.IsFalse(set.RemoveAction(in pool, ActionType.BasicSynthesis)); + Assert.IsTrue(set.RemoveAction(ActionType.BasicSynthesis)); + Assert.IsFalse(set.RemoveAction(ActionType.BasicSynthesis)); - Assert.IsTrue(set.AddAction(in pool, ActionType.BasicSynthesis)); - Assert.IsTrue(set.AddAction(in pool, ActionType.WasteNot2)); + Assert.IsTrue(set.AddAction(ActionType.BasicSynthesis)); + Assert.IsTrue(set.AddAction(ActionType.WasteNot2)); - Assert.IsTrue(set.RemoveAction(in pool, ActionType.BasicSynthesis)); - Assert.IsTrue(set.RemoveAction(in pool, ActionType.WasteNot2)); + Assert.IsTrue(set.RemoveAction(ActionType.BasicSynthesis)); + Assert.IsTrue(set.RemoveAction(ActionType.WasteNot2)); } [TestMethod] @@ -108,18 +46,18 @@ public class ActionSetTests { var set = new ActionSet(); - set.AddAction(in pool, ActionType.BasicSynthesis); + set.AddAction(ActionType.BasicSynthesis); - Assert.IsTrue(set.HasAction(in pool, ActionType.BasicSynthesis)); - Assert.IsFalse(set.HasAction(in pool, ActionType.WasteNot2)); + Assert.IsTrue(set.HasAction(ActionType.BasicSynthesis)); + Assert.IsFalse(set.HasAction(ActionType.WasteNot2)); - set.AddAction(in pool, ActionType.WasteNot2); - Assert.IsTrue(set.HasAction(in pool, ActionType.BasicSynthesis)); - Assert.IsTrue(set.HasAction(in pool, ActionType.WasteNot2)); + set.AddAction(ActionType.WasteNot2); + Assert.IsTrue(set.HasAction(ActionType.BasicSynthesis)); + Assert.IsTrue(set.HasAction(ActionType.WasteNot2)); - set.RemoveAction(in pool, ActionType.BasicSynthesis); - Assert.IsFalse(set.HasAction(in pool, ActionType.BasicSynthesis)); - Assert.IsTrue(set.HasAction(in pool, ActionType.WasteNot2)); + set.RemoveAction(ActionType.BasicSynthesis); + Assert.IsFalse(set.HasAction(ActionType.BasicSynthesis)); + Assert.IsTrue(set.HasAction(ActionType.WasteNot2)); } [TestMethod] @@ -127,25 +65,25 @@ public class ActionSetTests { var set = new ActionSet(); - set.AddAction(in pool, ActionType.BasicSynthesis); - set.AddAction(in pool, ActionType.ByregotsBlessing); - set.AddAction(in pool, ActionType.DelicateSynthesis); - set.AddAction(in pool, ActionType.Reflect); + set.AddAction(ActionType.BasicSynthesis); + set.AddAction(ActionType.ByregotsBlessing); + set.AddAction(ActionType.DelicateSynthesis); + set.AddAction(ActionType.Reflect); Assert.AreEqual(4, set.Count); - Assert.AreEqual(ActionType.BasicSynthesis, set.ElementAt(in pool, 0)); - Assert.AreEqual(ActionType.ByregotsBlessing, set.ElementAt(in pool, 1)); - Assert.AreEqual(ActionType.DelicateSynthesis, set.ElementAt(in pool, 2)); - Assert.AreEqual(ActionType.Reflect, set.ElementAt(in pool, 3)); + Assert.AreEqual(ActionType.BasicSynthesis, set.ElementAt(0)); + Assert.AreEqual(ActionType.ByregotsBlessing, set.ElementAt(1)); + Assert.AreEqual(ActionType.DelicateSynthesis, set.ElementAt(2)); + Assert.AreEqual(ActionType.Reflect, set.ElementAt(3)); - set.RemoveAction(in pool, ActionType.Reflect); + set.RemoveAction(ActionType.Reflect); Assert.AreEqual(3, set.Count); - Assert.AreEqual(ActionType.BasicSynthesis, set.ElementAt(in pool, 0)); - Assert.AreEqual(ActionType.ByregotsBlessing, set.ElementAt(in pool, 1)); - Assert.AreEqual(ActionType.DelicateSynthesis, set.ElementAt(in pool, 2)); + Assert.AreEqual(ActionType.BasicSynthesis, set.ElementAt(0)); + Assert.AreEqual(ActionType.ByregotsBlessing, set.ElementAt(1)); + Assert.AreEqual(ActionType.DelicateSynthesis, set.ElementAt(2)); } [TestMethod] @@ -165,13 +103,13 @@ public class ActionSetTests var set = new ActionSet(); foreach(var action in actions) - set.AddAction(in pool, action); + set.AddAction(action); var counts = new Dictionary(); var rng = new Random(0); for (var i = 0; i < 100; i++) { - var action = set.SelectRandom(in pool, rng); + var action = set.SelectRandom(rng); CollectionAssert.Contains(actions, action);