diff --git a/Benchmark/Program.cs b/Benchmark/Program.cs index 6ebdbdc..f15d43c 100644 --- a/Benchmark/Program.cs +++ b/Benchmark/Program.cs @@ -46,22 +46,22 @@ internal static class Program var config = new SolverConfig() { - Iterations = 30_000, - ThreadCount = 8, + Iterations = 300_000, + ForkCount = 8, }; Debugger.Break(); var s = Stopwatch.StartNew(); if (true) { - (_, var state) = Solver.Crafty.Solver.SearchStepwise(config, input, a => Console.WriteLine(a)); + (_, var state) = Solver.Crafty.Solver.SearchStepwiseForked(config, input, a => Console.WriteLine(a)); Console.WriteLine($"Qual: {state.Quality}/{state.Input.Recipe.MaxQuality}"); } else { - //(var actions, _) = SolverUtils.SearchOneshot(config, input); - //foreach (var action in actions) - // Console.Write($">{action.IntName()}"); - //Console.WriteLine(); + 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}"); } s.Stop(); Console.WriteLine($"{s.Elapsed.TotalMilliseconds:0.00}"); diff --git a/Solver/Crafty/ActionSet.cs b/Solver/Crafty/ActionSet.cs index cd1d067..ef7fa93 100644 --- a/Solver/Crafty/ActionSet.cs +++ b/Solver/Crafty/ActionSet.cs @@ -7,7 +7,7 @@ namespace Craftimizer.Solver.Crafty; public struct ActionSet { - private const bool IsDeterministic = true; + private const bool IsDeterministic = false; private uint bits; diff --git a/Solver/Crafty/Simulator.cs b/Solver/Crafty/Simulator.cs index 378a485..04d7574 100644 --- a/Solver/Crafty/Simulator.cs +++ b/Solver/Crafty/Simulator.cs @@ -95,9 +95,9 @@ public sealed class Simulator : SimulatorNoRandom return false; // use First Turn actions if it's available and the craft is difficult - if (baseAction.Category != ActionCategory.FirstTurn && + if (IsFirstStep && Input.Recipe.ClassJobLevel == 90 && - StepCount == 1 && + baseAction.Category != ActionCategory.FirstTurn && CP > 10) return false; @@ -107,6 +107,11 @@ public sealed class Simulator : SimulatorNoRandom baseAction.IncreasesQuality) return false; + // don't allow pure quality moves when it won't be able to finish the craft + if (baseAction.IncreasesQuality && + CalculateDurabilityCost(baseAction.DurabilityCost) > Durability) + return false; + if (baseAction.IncreasesProgress) { var progressIncrease = CalculateProgressGain(baseAction.Efficiency(this)); diff --git a/Solver/Crafty/Solver.cs b/Solver/Crafty/Solver.cs index a6f4016..3328133 100644 --- a/Solver/Crafty/Solver.cs +++ b/Solver/Crafty/Solver.cs @@ -1,5 +1,6 @@ -using Craftimizer.Simulator.Actions; using Craftimizer.Simulator; +using Craftimizer.Simulator.Actions; +using System.Diagnostics; using System.Diagnostics.Contracts; using System.Numerics; using System.Runtime.CompilerServices; @@ -16,7 +17,7 @@ public sealed class Solver public float MaxScore => rootScores.MaxScore; - public Solver(SolverConfig config, SimulationState state, bool strict) + public Solver(SolverConfig config, SimulationState state) { this.config = config; var sim = new Simulator(state, config.MaxStepCount); @@ -24,7 +25,7 @@ public sealed class Solver state, null, sim.CompletionState, - sim.AvailableActionsHeuristic(strict) + sim.AvailableActionsHeuristic(config.StrictActions) )); rootScores = new(); } @@ -206,16 +207,18 @@ public sealed class Solver var currentCompletionState = expandedNode.State.SimulationCompletionState; var currentActions = expandedNode.State.AvailableActions; + byte actionCount = 0; Span actions = stackalloc ActionType[Math.Min(config.MaxStepCount - currentState.ActionCount, config.MaxRolloutStepCount)]; - while (actionCount < actions.Length) + while (SimulationNode.GetCompletionState(currentCompletionState, currentActions) == CompletionState.Incomplete && + actionCount < actions.Length) { - if (SimulationNode.GetCompletionState(currentCompletionState, currentActions) != CompletionState.Incomplete) - break; var nextAction = currentActions.SelectRandom(random); actions[actionCount++] = nextAction; (_, currentState) = simulator.Execute(currentState, nextAction); currentCompletionState = simulator.CompletionState; + if (currentCompletionState != CompletionState.Incomplete) + break; currentActions = simulator.AvailableActionsHeuristic(true); } @@ -248,11 +251,11 @@ public sealed class Solver } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Search(CancellationToken token) + private void Search(CancellationToken token, int iterations) { Simulator simulator = new(rootNode.State.State, config.MaxStepCount); var random = rootNode.State.State.Input.Random; - for (var i = 0; i < config.Iterations; i++) + for (var i = 0; i < iterations; i++) { if (token.IsCancellationRequested) break; @@ -264,43 +267,48 @@ public sealed class Solver } } - public static (List Actions, SimulationState State) SearchStepwiseForked(SolverConfig config, int forkCount, SimulationInput input, Action? actionCallback, CancellationToken token = default) => - SearchStepwiseForked(config, forkCount, new SimulationState(input), actionCallback, token); + public static (List Actions, SimulationState State) SearchStepwiseForked(SolverConfig config, SimulationInput input, Action? actionCallback, CancellationToken token = default) => + SearchStepwiseForked(config, new SimulationState(input), actionCallback, token); - public static (List Actions, SimulationState State) SearchStepwiseForked(SolverConfig config, int forkCount, SimulationState state, Action? actionCallback, CancellationToken token = default) + public static (List Actions, SimulationState State) SearchStepwiseForked(SolverConfig config, SimulationState state, Action? actionCallback, CancellationToken token = default) { var actions = new List(); var sim = new Simulator(state, config.MaxStepCount); - while (!sim.IsComplete) + while (true) { if (token.IsCancellationRequested) break; - var tasks = new Task<(float score, List actions, SimulationState state)>[forkCount]; - for (var i = 0; i < forkCount; ++i) + if (sim.IsComplete) + break; + + + var s = Stopwatch.StartNew(); + var tasks = new Task<(float MaxScore, (List Actions, SimulationNode Node) Solution)>[config.ForkCount]; + for (var i = 0; i < config.ForkCount; ++i) tasks[i] = Task.Run(() => { - var solver = new Solver(config, state, true); - solver.Search(token); - var (solution_actions, solution_node) = solver.Solution(); - - return (solver.MaxScore, solution_actions, solution_node.State); + var solver = new Solver(config, state); + solver.Search(token, config.Iterations / config.ForkCount); + return (solver.MaxScore, solver.Solution()); }, token); Task.WaitAll(tasks, CancellationToken.None); + s.Stop(); - var (score, solution_actions, solution_state) = tasks.Select(t => t.Result).MaxBy(r => r.score); + var (maxScore, (solutionActions, solutionNode)) = tasks.Select(t => t.Result).MaxBy(r => r.MaxScore); - if (score >= 1.0) + if (maxScore >= config.ScoreStorageThreshold) { - actions.AddRange(solution_actions); - return (actions, solution_state); + actions.AddRange(solutionActions); + return (actions, solutionNode.State); } - var chosen_action = solution_actions[0]; + 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"); + (_, state) = sim.Execute(state, chosen_action); actions.Add(chosen_action); - - actionCallback?.Invoke(chosen_action); } return (actions, state); @@ -313,40 +321,65 @@ public sealed class Solver { var actions = new List(); var sim = new Simulator(state, config.MaxStepCount); - var solver = new Solver(config, state, true); - while (!sim.IsComplete) + while (true) { if (token.IsCancellationRequested) break; - solver.Search(token); + if (sim.IsComplete) + break; + + var solver = new Solver(config, state); + + var s = Stopwatch.StartNew(); + solver.Search(token, config.Iterations); + s.Stop(); + var (solution_actions, solution_node) = solver.Solution(); - if (solver.MaxScore >= 1.0) + if (solver.MaxScore >= config.ScoreStorageThreshold) { actions.AddRange(solution_actions); return (actions, solution_node.State); } var chosen_action = solution_actions[0]; + actionCallback?.Invoke(chosen_action); + Console.WriteLine($"{s.Elapsed.TotalMilliseconds:0.00}ms {config.Iterations / s.Elapsed.TotalSeconds / 1000:0.00} kI/s"); + (_, state) = sim.Execute(state, chosen_action); actions.Add(chosen_action); - - actionCallback?.Invoke(chosen_action); - - solver = new Solver(config, state, true); } return (actions, state); } + public static (List Actions, SimulationState State) SearchOneshotForked(SolverConfig config, SimulationInput input, CancellationToken token = default) => + SearchOneshotForked(config, new SimulationState(input), token); + + public static (List Actions, SimulationState State) SearchOneshotForked(SolverConfig config, SimulationState state, CancellationToken token = default) + { + var tasks = new Task<(float MaxScore, (List Actions, SimulationNode Node) Solution)>[config.ForkCount]; + for (var i = 0; i < config.ForkCount; ++i) + tasks[i] = Task.Run(() => + { + var solver = new Solver(config, state); + solver.Search(token, config.Iterations / config.ForkCount); + return (solver.MaxScore, solver.Solution()); + }, token); + Task.WaitAll(tasks, CancellationToken.None); + + var (solutionActions, solutionNode) = tasks.Select(t => t.Result).MaxBy(r => r.MaxScore).Solution; + return (solutionActions, solutionNode.State); + } + public static (List Actions, SimulationState State) SearchOneshot(SolverConfig config, SimulationInput input, CancellationToken token = default) => SearchOneshot(config, new SimulationState(input), token); public static (List Actions, SimulationState State) SearchOneshot(SolverConfig config, SimulationState state, CancellationToken token = default) { - var solver = new Solver(config, state, false); - solver.Search(token); + var solver = new Solver(config, state); + solver.Search(token, config.Iterations); 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 index e970cfd..798540c 100644 --- a/Solver/Crafty/SolverConfig.cs +++ b/Solver/Crafty/SolverConfig.cs @@ -11,7 +11,8 @@ public readonly record struct SolverConfig public float ExplorationConstant { get; init; } public int MaxStepCount { get; init; } public int MaxRolloutStepCount { get; init; } - public int ThreadCount { get; init; } + public int ForkCount { get; init; } + public bool StrictActions { get; init; } public SolverConfig() { @@ -21,6 +22,7 @@ public readonly record struct SolverConfig ExplorationConstant = 4; MaxStepCount = 25; MaxRolloutStepCount = MaxStepCount; - ThreadCount = Environment.ProcessorCount; + ForkCount = Environment.ProcessorCount; + StrictActions = true; } }