From a9541e2e1d1a8db4b0587e28aef39fbdde7f1cde Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Sun, 9 Jul 2023 08:59:39 +0200 Subject: [PATCH] Add furcated actions (better results than single thread & faster!) --- Benchmark/Program.cs | 87 +++++++++++++++++++++++++-------- Solver/Crafty/SimulationNode.cs | 22 ++++++++- Solver/Crafty/Solver.cs | 81 ++++++++++++++++++++++++++++-- Solver/Crafty/SolverConfig.cs | 2 + 4 files changed, 165 insertions(+), 27 deletions(-) diff --git a/Benchmark/Program.cs b/Benchmark/Program.cs index f15d43c..e39da6e 100644 --- a/Benchmark/Program.cs +++ b/Benchmark/Program.cs @@ -1,8 +1,5 @@ -using BenchmarkDotNet.Running; using Craftimizer.Simulator; -using Craftimizer.Simulator.Actions; using Craftimizer.Solver.Crafty; -using ObjectLayoutInspector; using System.Diagnostics; namespace Craftimizer.Benchmark; @@ -18,14 +15,15 @@ internal static class Program //return; var input = new SimulationInput( - new CharacterStats { - Craftsmanship = 4041, - Control = 3905, - CP = 609, + new CharacterStats + { + Craftsmanship = 4078, + Control = 3897, + CP = 704, Level = 90, CanUseManipulation = true, - HasSplendorousBuff = true, - IsSpecialist = true, + HasSplendorousBuff = false, + IsSpecialist = false, CLvl = 560, }, new RecipeInfo() @@ -46,25 +44,72 @@ internal static class Program var config = new SolverConfig() { - Iterations = 300_000, + Iterations = 100_000, ForkCount = 8, }; - Debugger.Break(); + for (var i = 0; i < 7; ++i) + { + Console.WriteLine($"{i + 1}"); + var c = config with { FurcatedActionCount = i + 1 }; + Benchmark(() => Solver.Crafty.Solver.SearchStepwiseFurcated(c, input).State); + } + } + + private static void Benchmark(Func search) + { var s = Stopwatch.StartNew(); - if (true) { - (_, var state) = Solver.Crafty.Solver.SearchStepwiseForked(config, input, a => Console.WriteLine(a)); - Console.WriteLine($"Qual: {state.Quality}/{state.Input.Recipe.MaxQuality}"); + List q = new(); + for (var i = 0; i < 60; ++i) + { + var state = search(); + //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, + }; + if (dblPercentage >= 100) return input[^1]; + + var position = (input.Count + 1) * dblPercentage / 100f; + var n = (dblPercentage / 100f * (input.Count - 1)) + 1; + + float leftNumber, rightNumber; + if (position >= 1) + { + leftNumber = input[(int)MathF.Floor(n) - 1]; + rightNumber = input[(int)MathF.Floor(n)]; } else { - var (actions, state) = Solver.Crafty.Solver.SearchOneshotForked(config, input); - foreach (var action in actions) - Console.WriteLine(action); - Console.WriteLine($"Qual: {state.Quality}/{state.Input.Recipe.MaxQuality}"); + 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)); } - s.Stop(); - Console.WriteLine($"{s.Elapsed.TotalMilliseconds:0.00}"); - Debugger.Break(); } } diff --git a/Solver/Crafty/SimulationNode.cs b/Solver/Crafty/SimulationNode.cs index ad2aef2..700e82f 100644 --- a/Solver/Crafty/SimulationNode.cs +++ b/Solver/Crafty/SimulationNode.cs @@ -32,6 +32,24 @@ public struct SimulationNode public readonly float? CalculateScore(int maxStepCount) => CalculateScoreForState(State, SimulationCompletionState, maxStepCount); + private static bool CanByregot(SimulationState state) + { + if (state.ActiveEffects.InnerQuiet == 0) + return false; + + var wasteNot = Math.Max(state.ActiveEffects.WasteNot, state.ActiveEffects.WasteNot2); + var manipulation = state.ActiveEffects.Manipulation; + var durability = state.Durability; + durability -= wasteNot-- > 0 ? 5 : 10; + if (durability <= 0) + return false; + if (manipulation-- > 0) + durability += 5; + durability -= wasteNot-- > 0 ? 5 : 10; + + return durability >= 0; + } + public static float? CalculateScoreForState(SimulationState state, CompletionState completionState, int maxStepCount) { if (completionState != CompletionState.ProgressComplete) @@ -52,9 +70,11 @@ public struct SimulationNode state.Input.Recipe.MaxProgress ); + var byregotBonus = CanByregot(state) ? (state.ActiveEffects.InnerQuiet * .2f + 1) * state.Input.BaseQualityGain : 0; + var quality = Math.Clamp(state.Quality + byregotBonus, 0, state.Input.Recipe.MaxQuality); var qualityScore = Apply( qualityBonus, - state.Quality, + quality, state.Input.Recipe.MaxQuality ); diff --git a/Solver/Crafty/Solver.cs b/Solver/Crafty/Solver.cs index 3328133..2f6f415 100644 --- a/Solver/Crafty/Solver.cs +++ b/Solver/Crafty/Solver.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Diagnostics.Contracts; using System.Numerics; using System.Runtime.CompilerServices; +using System.Threading.Tasks; using Node = Craftimizer.Solver.Crafty.ArenaNode; namespace Craftimizer.Solver.Crafty; @@ -267,10 +268,80 @@ public sealed class Solver } } - public static (List Actions, SimulationState State) SearchStepwiseForked(SolverConfig config, SimulationInput input, Action? actionCallback, CancellationToken token = default) => + public static (List Actions, SimulationState State) SearchStepwiseFurcated(SolverConfig config, SimulationInput input, CancellationToken token = default) => + SearchStepwiseFurcated(config, new SimulationState(input), token); + + public static (List Actions, SimulationState State) SearchStepwiseFurcated(SolverConfig config, SimulationState state, CancellationToken token = default) + { + var bestSims = new List<(float Score, (List Actions, SimulationState State) Result)>(); + + var sim = new Simulator(state, config.MaxStepCount); + + var activeStates = new List<(List Actions, SimulationState State)>() { (new(), state) }; + + while (activeStates.Count != 0) + { + if (token.IsCancellationRequested) + break; + + var s = Stopwatch.StartNew(); + var tasks = new List Actions, SimulationNode Node) Solution)>>(config.ForkCount); + for (var i = 0; i < config.ForkCount; i++) + { + var stateIdx = (int)((float)i / config.ForkCount * activeStates.Count); + var st = activeStates[stateIdx]; + tasks.Add( + Task.Run(() => + { + var solver = new Solver(config, activeStates[stateIdx].State); + solver.Search(token, config.Iterations / config.ForkCount); + return (solver.MaxScore, stateIdx, solver.Solution()); + }, token) + ); + } + Task.WaitAll(tasks.ToArray(), CancellationToken.None); + s.Stop(); + + var bestActions = tasks.Select(t => t.Result).OrderByDescending(r => r.MaxScore).Take(config.FurcatedActionCount).ToArray(); + + var bestAction = bestActions[0]; + if (bestAction.MaxScore >= config.ScoreStorageThreshold) + { + var (maxScore, furcatedActionIdx, (solutionActions, solutionNode)) = bestAction; + var (activeActions, activeState) = activeStates[furcatedActionIdx]; + + activeActions.AddRange(solutionActions); + return (activeActions, solutionNode.State); + } + + var newStates = new List<(List Actions, SimulationState State)>(config.FurcatedActionCount); + for (var i = 0; i < bestActions.Length; ++i) + { + var (maxScore, furcatedActionIdx, (solutionActions, solutionNode)) = bestActions[i]; + var (activeActions, activeState) = activeStates[furcatedActionIdx]; + + var chosenAction = solutionActions[0]; + + var newActions = new List(activeActions) { chosenAction }; + var newState = sim.Execute(activeState, chosenAction).NewState; + if (sim.IsComplete) + bestSims.Add((maxScore, (newActions, newState))); + else + newStates.Add((newActions, newState)); + } + + activeStates = newStates; + + Console.WriteLine($"{s.Elapsed.TotalMilliseconds:0.00}ms {config.Iterations / config.ForkCount / s.Elapsed.TotalSeconds / 1000:0.00} kI/s/t"); + } + + return bestSims.MaxBy(s => s.Score).Result; + } + + public static (List Actions, SimulationState State) SearchStepwiseForked(SolverConfig config, SimulationInput input, Action? actionCallback = null, CancellationToken token = default) => SearchStepwiseForked(config, new SimulationState(input), actionCallback, token); - public static (List Actions, SimulationState State) SearchStepwiseForked(SolverConfig config, SimulationState state, Action? actionCallback, CancellationToken token = default) + public static (List Actions, SimulationState State) SearchStepwiseForked(SolverConfig config, SimulationState state, Action? actionCallback = null, CancellationToken token = default) { var actions = new List(); var sim = new Simulator(state, config.MaxStepCount); @@ -305,7 +376,7 @@ public sealed class Solver var chosen_action = solutionActions[0]; actionCallback?.Invoke(chosen_action); - Console.WriteLine($"{s.Elapsed.TotalMilliseconds:0.00}ms {config.Iterations / s.Elapsed.TotalSeconds / 1000:0.00} kI/s"); + Console.WriteLine($"{s.Elapsed.TotalMilliseconds:0.00}ms {config.Iterations / config.ForkCount / s.Elapsed.TotalSeconds / 1000:0.00} kI/s/t"); (_, state) = sim.Execute(state, chosen_action); actions.Add(chosen_action); @@ -314,10 +385,10 @@ public sealed class Solver return (actions, state); } - public static (List Actions, SimulationState State) SearchStepwise(SolverConfig config, SimulationInput input, Action? actionCallback, CancellationToken token = default) => + public static (List Actions, SimulationState State) SearchStepwise(SolverConfig config, SimulationInput input, Action? actionCallback = null, CancellationToken token = default) => SearchStepwise(config, new SimulationState(input), actionCallback, token); - public static (List Actions, SimulationState State) SearchStepwise(SolverConfig config, SimulationState state, Action? actionCallback, CancellationToken token = default) + public static (List Actions, SimulationState State) SearchStepwise(SolverConfig config, SimulationState state, Action? actionCallback = null, CancellationToken token = default) { var actions = new List(); var sim = new Simulator(state, config.MaxStepCount); diff --git a/Solver/Crafty/SolverConfig.cs b/Solver/Crafty/SolverConfig.cs index 798540c..5e68b66 100644 --- a/Solver/Crafty/SolverConfig.cs +++ b/Solver/Crafty/SolverConfig.cs @@ -12,6 +12,7 @@ public readonly record struct SolverConfig public int MaxStepCount { get; init; } public int MaxRolloutStepCount { get; init; } public int ForkCount { get; init; } + public int FurcatedActionCount { get; init; } public bool StrictActions { get; init; } public SolverConfig() @@ -23,6 +24,7 @@ public readonly record struct SolverConfig MaxStepCount = 25; MaxRolloutStepCount = MaxStepCount; ForkCount = Environment.ProcessorCount; + FurcatedActionCount = ForkCount / 2; StrictActions = true; } }