diff --git a/Benchmark/Program.cs b/Benchmark/Program.cs index 280e367..1fea691 100644 --- a/Benchmark/Program.cs +++ b/Benchmark/Program.cs @@ -1,18 +1,30 @@ using Craftimizer.Simulator; using Craftimizer.Simulator.Actions; using Craftimizer.Solver; -using System.Diagnostics; namespace Craftimizer.Benchmark; internal static class Program { - private static async Task Main(string[] args) + private static Task Main(string[] args) { #if !IS_TRACE + RunBench(args); + return Task.CompletedTask; +#else + return RunTrace(); +#endif + // return RunOther(); + } + + private static void RunBench(string[] args) + { Environment.SetEnvironmentVariable("IS_BENCH", "1"); BenchmarkDotNet.Running.BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); -#else + } + + private static async Task RunTrace() + { var input = new SimulationInput( new() { @@ -43,143 +55,88 @@ internal static class Program var config = new SolverConfig() { Algorithm = SolverAlgorithm.Stepwise, - Iterations = 30000 + Iterations = 30000, + MaxStepCount = 25 }; var solver = new Solver.Solver(config, new(input)); solver.OnNewAction += s => Console.WriteLine($">{s}"); solver.Start(); var (_, s) = await solver.GetTask().ConfigureAwait(false); Console.WriteLine($"Qual: {s.Quality}/{s.Input.Recipe.MaxQuality}"); -#endif - return; - - ////TypeLayout.PrintLayout>(true); - ////return; - - //var input = new SimulationInput( - // new CharacterStats - // { - // Craftsmanship = 4078, - // Control = 3897, - // CP = 704, - // Level = 90, - // CanUseManipulation = true, - // HasSplendorousBuff = false, - // IsSpecialist = false, - // CLvl = 560, - // }, - // new RecipeInfo() - // { - // IsExpert = false, - // ClassJobLevel = 90, - // RLvl = 640, - // ConditionsFlag = 15, - // MaxDurability = 70, - // MaxQuality = 14040, - // MaxProgress = 6600, - // QualityModifier = 70, - // QualityDivider = 115, - // ProgressModifier = 80, - // ProgressDivider = 130, - // } - //); - - //var config = new SolverConfig() - //{ - // Iterations = 100_000, - // ForkCount = 32, - // FurcatedActionCount = 16, - // MaxStepCount = 30, - //}; - - //var sim = new SimulatorNoRandom(new(input)); - //(_, var state) = sim.Execute(new(input), ActionType.MuscleMemory); - //(_, state) = sim.Execute(state, ActionType.PrudentTouch); - ////(_, state) = sim.Execute(state, ActionType.Manipulation); - ////(_, state) = sim.Execute(state, ActionType.Veneration); - ////(_, state) = sim.Execute(state, ActionType.WasteNot); - ////(_, state) = sim.Execute(state, ActionType.Groundwork); - ////(_, state) = sim.Execute(state, ActionType.Groundwork); - ////(_, state) = sim.Execute(state, ActionType.Groundwork); - ////(_, state) = sim.Execute(state, ActionType.Innovation); - ////(_, state) = sim.Execute(state, ActionType.PrudentTouch); - ////(_, state) = sim.Execute(state, ActionType.AdvancedTouchCombo); - ////(_, state) = sim.Execute(state, ActionType.Manipulation); - ////(_, state) = sim.Execute(state, ActionType.Innovation); - ////(_, state) = sim.Execute(state, ActionType.PrudentTouch); - ////(_, state) = sim.Execute(state, ActionType.AdvancedTouchCombo); - ////(_, state) = sim.Execute(state, ActionType.GreatStrides); - ////(_, state) = sim.Execute(state, ActionType.Innovation); - ////(_, state) = sim.Execute(state, ActionType.FocusedTouchCombo); - ////(_, state) = sim.Execute(state, ActionType.GreatStrides); - ////(_, state) = sim.Execute(state, ActionType.ByregotsBlessing); - ////(_, state) = sim.Execute(state, ActionType.CarefulSynthesis); - ////(_, state) = sim.Execute(state, ActionType.CarefulSynthesis); - - //Console.WriteLine($"{state.Quality} {state.CP} {state.Progress} {state.Durability}"); - ////return; - //var solver = new Solver.Solver(config, state); - //solver.OnLog += Console.WriteLine; - //solver.OnNewAction += s => Console.WriteLine(s); - //solver.Start(); - //var (_, s) = await solver.GetTask().ConfigureAwait(false); - //Console.WriteLine($"Qual: {s.Quality}/{s.Input.Recipe.MaxQuality}"); } - private static void Benchmark(Func search) + private static async Task RunOther() { - var s = Stopwatch.StartNew(); - List q = new(); - for (var i = 0; i < 15; ++i) + //TypeLayout.PrintLayout>(true); + //return; + + var input = new SimulationInput( + new CharacterStats + { + Craftsmanship = 4078, + Control = 3897, + CP = 704, + Level = 90, + CanUseManipulation = true, + HasSplendorousBuff = false, + IsSpecialist = false, + CLvl = 560, + }, + new RecipeInfo() + { + IsExpert = false, + ClassJobLevel = 90, + RLvl = 640, + ConditionsFlag = 15, + MaxDurability = 70, + MaxQuality = 14040, + MaxProgress = 6600, + QualityModifier = 70, + QualityDivider = 115, + ProgressModifier = 80, + ProgressDivider = 130, + } + ); + + var config = new SolverConfig() { - var state = search().State; - //Console.WriteLine($"Qual: {state.Quality}/{state.Input.Recipe.MaxQuality}"); - - q.Add(state.Quality); - } - - s.Stop(); - Console.WriteLine($"{s.Elapsed.TotalMilliseconds / 60:0.00}ms/cycle"); - Console.WriteLine(string.Join(',', q)); - q.Sort(); - Console.WriteLine($"Min: {Quartile(q, 0)}, Max: {Quartile(q, 4)}, Avg: {Quartile(q, 2)}, Q1: {Quartile(q, 1)}, Q3: {Quartile(q, 3)}"); - } - - // https://stackoverflow.com/a/31536435 - private static float Quartile(List input, int quartile) - { - float dblPercentage = quartile switch - { - 0 => 0, // Smallest value in the data set - 1 => 25, // First quartile (25th percentile) - 2 => 50, // Second quartile (50th percentile) - 3 => 75, // Third quartile (75th percentile) - 4 => 100, // Largest value in the data set - _ => 0, + Iterations = 100_000, + ForkCount = 32, + FurcatedActionCount = 16, + MaxStepCount = 30, }; - if (dblPercentage >= 100) return input[^1]; - var position = (input.Count + 1) * dblPercentage / 100f; - var n = (dblPercentage / 100f * (input.Count - 1)) + 1; + var sim = new SimulatorNoRandom(new(input)); + (_, var state) = sim.Execute(new(input), ActionType.MuscleMemory); + (_, state) = sim.Execute(state, ActionType.PrudentTouch); + //(_, state) = sim.Execute(state, ActionType.Manipulation); + //(_, state) = sim.Execute(state, ActionType.Veneration); + //(_, state) = sim.Execute(state, ActionType.WasteNot); + //(_, state) = sim.Execute(state, ActionType.Groundwork); + //(_, state) = sim.Execute(state, ActionType.Groundwork); + //(_, state) = sim.Execute(state, ActionType.Groundwork); + //(_, state) = sim.Execute(state, ActionType.Innovation); + //(_, state) = sim.Execute(state, ActionType.PrudentTouch); + //(_, state) = sim.Execute(state, ActionType.AdvancedTouchCombo); + //(_, state) = sim.Execute(state, ActionType.Manipulation); + //(_, state) = sim.Execute(state, ActionType.Innovation); + //(_, state) = sim.Execute(state, ActionType.PrudentTouch); + //(_, state) = sim.Execute(state, ActionType.AdvancedTouchCombo); + //(_, state) = sim.Execute(state, ActionType.GreatStrides); + //(_, state) = sim.Execute(state, ActionType.Innovation); + //(_, state) = sim.Execute(state, ActionType.FocusedTouchCombo); + //(_, state) = sim.Execute(state, ActionType.GreatStrides); + //(_, state) = sim.Execute(state, ActionType.ByregotsBlessing); + //(_, state) = sim.Execute(state, ActionType.CarefulSynthesis); + //(_, state) = sim.Execute(state, ActionType.CarefulSynthesis); - float leftNumber, rightNumber; - if (position >= 1) - { - leftNumber = input[(int)MathF.Floor(n) - 1]; - rightNumber = input[(int)MathF.Floor(n)]; - } - else - { - leftNumber = input[0]; // first data - rightNumber = input[1]; // first data - } - - if (leftNumber == rightNumber) - return leftNumber; - else - { - var part = n - MathF.Floor(n); - return leftNumber + (part * (rightNumber - leftNumber)); - } + Console.WriteLine($"{state.Quality} {state.CP} {state.Progress} {state.Durability}"); + //return; + var solver = new Solver.Solver(config, state); + solver.OnLog += Console.WriteLine; + solver.OnNewAction += s => Console.WriteLine(s); + solver.Start(); + var (_, s) = await solver.GetTask().ConfigureAwait(false); + Console.WriteLine($"Qual: {s.Quality}/{s.Input.Recipe.MaxQuality}"); } } diff --git a/Craftimizer.Test/Craftimizer.Test.csproj b/Craftimizer.Test/Craftimizer.Test.csproj index 520abba..5c59fdb 100644 --- a/Craftimizer.Test/Craftimizer.Test.csproj +++ b/Craftimizer.Test/Craftimizer.Test.csproj @@ -8,7 +8,7 @@ false true x64 - Debug;Release + Debug;Release;Trace @@ -22,9 +22,14 @@ - + $(DefineConstants);IS_DETERMINISTIC + + True + $(DefineConstants);IS_DETERMINISTIC;IS_TRACE + + diff --git a/Craftimizer.Test/Solver/ActionSet.cs b/Craftimizer.Test/Solver/ActionSet.cs index b6fb34f..d34e387 100644 --- a/Craftimizer.Test/Solver/ActionSet.cs +++ b/Craftimizer.Test/Solver/ActionSet.cs @@ -89,33 +89,47 @@ public class ActionSetTests Assert.AreEqual(4, set.Count); +#if !IS_TRACE Assert.AreEqual(ActionType.DelicateSynthesis, set.ElementAt(0)); Assert.AreEqual(ActionType.FocusedTouch, set.ElementAt(1)); Assert.AreEqual(ActionType.ByregotsBlessing, set.ElementAt(2)); Assert.AreEqual(ActionType.BasicSynthesis, set.ElementAt(3)); +#else + Assert.AreEqual(ActionType.BasicSynthesis, set.ElementAt(0)); + Assert.AreEqual(ActionType.ByregotsBlessing, set.ElementAt(1)); + Assert.AreEqual(ActionType.FocusedTouch, set.ElementAt(2)); + Assert.AreEqual(ActionType.DelicateSynthesis, set.ElementAt(3)); +#endif set.RemoveAction(ActionType.FocusedTouch); Assert.AreEqual(3, set.Count); + +#if !IS_TRACE Assert.AreEqual(ActionType.DelicateSynthesis, set.ElementAt(0)); Assert.AreEqual(ActionType.ByregotsBlessing, set.ElementAt(1)); Assert.AreEqual(ActionType.BasicSynthesis, set.ElementAt(2)); +#else + Assert.AreEqual(ActionType.BasicSynthesis, set.ElementAt(0)); + Assert.AreEqual(ActionType.ByregotsBlessing, set.ElementAt(1)); + Assert.AreEqual(ActionType.DelicateSynthesis, set.ElementAt(2)); +#endif } [TestMethod] public void TestRandomIndex() { #if IS_DETERMINISTIC - Assert.Inconclusive("Craftimizer is built for benchmarking; all random actions are deterministic and not actually random."); + Assert.Inconclusive("Craftimizer is currently built for determinism; all random actions are not actually random."); #endif var actions = new[] - { - ActionType.BasicTouch, - ActionType.BasicSynthesis, - ActionType.GreatStrides, - ActionType.TrainedFinesse, - }; + { + ActionType.BasicTouch, + ActionType.BasicSynthesis, + ActionType.GreatStrides, + ActionType.TrainedFinesse, + }; var set = new ActionSet(); foreach(var action in actions) diff --git a/Craftimizer.sln b/Craftimizer.sln index b8a6abd..3f01805 100644 --- a/Craftimizer.sln +++ b/Craftimizer.sln @@ -53,7 +53,8 @@ Global {C3AEA981-9DA8-405C-995B-86528493891B}.Debug|x64.Build.0 = Debug|x64 {C3AEA981-9DA8-405C-995B-86528493891B}.Release|x64.ActiveCfg = Release|x64 {C3AEA981-9DA8-405C-995B-86528493891B}.Release|x64.Build.0 = Release|x64 - {C3AEA981-9DA8-405C-995B-86528493891B}.Trace|x64.ActiveCfg = Release|x64 + {C3AEA981-9DA8-405C-995B-86528493891B}.Trace|x64.ActiveCfg = Trace|x64 + {C3AEA981-9DA8-405C-995B-86528493891B}.Trace|x64.Build.0 = Trace|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Solver/ActionSet.cs b/Solver/ActionSet.cs index 98c0b9f..9d73151 100644 --- a/Solver/ActionSet.cs +++ b/Solver/ActionSet.cs @@ -11,6 +11,7 @@ public struct ActionSet public static readonly ActionType[] AcceptedActions = new[] { +#if !IS_TRACE ActionType.StandardTouchCombo, ActionType.AdvancedTouchCombo, ActionType.FocusedTouchCombo, @@ -40,6 +41,36 @@ public struct ActionSet ActionType.Observe, ActionType.MastersMend, ActionType.BasicTouch, +#else + //ActionType.BasicSynthesis, + ActionType.BasicTouch, + ActionType.MastersMend, + ActionType.Observe, + ActionType.WasteNot, + ActionType.Veneration, + ActionType.StandardTouch, + ActionType.GreatStrides, + ActionType.Innovation, + ActionType.BasicSynthesis, + ActionType.WasteNot2, + ActionType.ByregotsBlessing, + ActionType.MuscleMemory, + //ActionType.CarefulSynthesis, + ActionType.Manipulation, + ActionType.PrudentTouch, + ActionType.FocusedSynthesis, + ActionType.FocusedTouch, + ActionType.Reflect, + ActionType.PreparatoryTouch, + //ActionType.Groundwork, + ActionType.DelicateSynthesis, + ActionType.TrainedEye, + ActionType.CarefulSynthesis, + ActionType.AdvancedTouch, + ActionType.Groundwork, + ActionType.PrudentSynthesis, + ActionType.TrainedFinesse, +#endif }; public static readonly int[] AcceptedActionsLUT; @@ -64,7 +95,12 @@ public struct ActionSet } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ActionType ToAction(int index) => AcceptedActions[index]; + private static ActionType ToAction(int index) + { + if (index < 0 || index >= AcceptedActions.Length) + throw new ArgumentOutOfRangeException(nameof(index), index, $"Index {index} is out of range for {nameof(ActionSet)}."); + return AcceptedActions[index]; + } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint ToMask(ActionType action) => 1u << (FromAction(action) + 1); diff --git a/Solver/SimulationNode.cs b/Solver/SimulationNode.cs index 3f8639e..7b77582 100644 --- a/Solver/SimulationNode.cs +++ b/Solver/SimulationNode.cs @@ -46,6 +46,9 @@ public struct SimulationNode if (completionState != CompletionState.ProgressComplete) return null; + if (state.Input.Recipe.MaxQuality == 0) + return 1f - ((float)(state.ActionCount + 1) / config.MaxStepCount); + static float Apply(float bonus, float value, float target) => bonus * (target > 0 ? Math.Min(1f, value / target) : 1); @@ -55,7 +58,7 @@ public struct SimulationNode state.Input.Recipe.MaxProgress ); - var byregotBonus = CanByregot(state) ? (state.ActiveEffects.InnerQuiet * .2f + 1) * state.Input.BaseQualityGain : 0; + var byregotBonus = 0;// CanByregot(state) ? (state.ActiveEffects.InnerQuiet * .2f + 1) * state.Input.BaseQualityGain : 0; var qualityScore = Apply( config.ScoreQuality, state.Quality + byregotBonus, @@ -75,7 +78,7 @@ public struct SimulationNode ); var fewerStepsScore = - config.ScoreSteps * (1f - (float)(state.ActionCount + 1) / config.MaxStepCount); + config.ScoreSteps * (1f - ((float)(state.ActionCount + 1) / config.MaxStepCount)); return progressScore + qualityScore + durabilityScore + cpScore + fewerStepsScore; }