More heuristic improvements

This commit is contained in:
Asriel Camora
2023-07-08 14:38:35 +02:00
parent 436858e65c
commit 2e2db97ca7
5 changed files with 88 additions and 48 deletions
+7 -7
View File
@@ -46,22 +46,22 @@ internal static class Program
var config = new SolverConfig() var config = new SolverConfig()
{ {
Iterations = 30_000, Iterations = 300_000,
ThreadCount = 8, ForkCount = 8,
}; };
Debugger.Break(); Debugger.Break();
var s = Stopwatch.StartNew(); var s = Stopwatch.StartNew();
if (true) { 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}"); Console.WriteLine($"Qual: {state.Quality}/{state.Input.Recipe.MaxQuality}");
} }
else else
{ {
//(var actions, _) = SolverUtils.SearchOneshot<SolverConcurrent>(config, input); var (actions, state) = Solver.Crafty.Solver.SearchOneshotForked(config, input);
//foreach (var action in actions) foreach (var action in actions)
// Console.Write($">{action.IntName()}"); Console.WriteLine(action);
//Console.WriteLine(); Console.WriteLine($"Qual: {state.Quality}/{state.Input.Recipe.MaxQuality}");
} }
s.Stop(); s.Stop();
Console.WriteLine($"{s.Elapsed.TotalMilliseconds:0.00}"); Console.WriteLine($"{s.Elapsed.TotalMilliseconds:0.00}");
+1 -1
View File
@@ -7,7 +7,7 @@ namespace Craftimizer.Solver.Crafty;
public struct ActionSet public struct ActionSet
{ {
private const bool IsDeterministic = true; private const bool IsDeterministic = false;
private uint bits; private uint bits;
+7 -2
View File
@@ -95,9 +95,9 @@ public sealed class Simulator : SimulatorNoRandom
return false; return false;
// use First Turn actions if it's available and the craft is difficult // use First Turn actions if it's available and the craft is difficult
if (baseAction.Category != ActionCategory.FirstTurn && if (IsFirstStep &&
Input.Recipe.ClassJobLevel == 90 && Input.Recipe.ClassJobLevel == 90 &&
StepCount == 1 && baseAction.Category != ActionCategory.FirstTurn &&
CP > 10) CP > 10)
return false; return false;
@@ -107,6 +107,11 @@ public sealed class Simulator : SimulatorNoRandom
baseAction.IncreasesQuality) baseAction.IncreasesQuality)
return false; 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) if (baseAction.IncreasesProgress)
{ {
var progressIncrease = CalculateProgressGain(baseAction.Efficiency(this)); var progressIncrease = CalculateProgressGain(baseAction.Efficiency(this));
+69 -36
View File
@@ -1,5 +1,6 @@
using Craftimizer.Simulator.Actions;
using Craftimizer.Simulator; using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions;
using System.Diagnostics;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -16,7 +17,7 @@ public sealed class Solver
public float MaxScore => rootScores.MaxScore; public float MaxScore => rootScores.MaxScore;
public Solver(SolverConfig config, SimulationState state, bool strict) public Solver(SolverConfig config, SimulationState state)
{ {
this.config = config; this.config = config;
var sim = new Simulator(state, config.MaxStepCount); var sim = new Simulator(state, config.MaxStepCount);
@@ -24,7 +25,7 @@ public sealed class Solver
state, state,
null, null,
sim.CompletionState, sim.CompletionState,
sim.AvailableActionsHeuristic(strict) sim.AvailableActionsHeuristic(config.StrictActions)
)); ));
rootScores = new(); rootScores = new();
} }
@@ -206,16 +207,18 @@ public sealed class Solver
var currentCompletionState = expandedNode.State.SimulationCompletionState; var currentCompletionState = expandedNode.State.SimulationCompletionState;
var currentActions = expandedNode.State.AvailableActions; var currentActions = expandedNode.State.AvailableActions;
byte actionCount = 0; byte actionCount = 0;
Span<ActionType> actions = stackalloc ActionType[Math.Min(config.MaxStepCount - currentState.ActionCount, config.MaxRolloutStepCount)]; Span<ActionType> 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); var nextAction = currentActions.SelectRandom(random);
actions[actionCount++] = nextAction; actions[actionCount++] = nextAction;
(_, currentState) = simulator.Execute(currentState, nextAction); (_, currentState) = simulator.Execute(currentState, nextAction);
currentCompletionState = simulator.CompletionState; currentCompletionState = simulator.CompletionState;
if (currentCompletionState != CompletionState.Incomplete)
break;
currentActions = simulator.AvailableActionsHeuristic(true); currentActions = simulator.AvailableActionsHeuristic(true);
} }
@@ -248,11 +251,11 @@ public sealed class Solver
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Search(CancellationToken token) private void Search(CancellationToken token, int iterations)
{ {
Simulator simulator = new(rootNode.State.State, config.MaxStepCount); Simulator simulator = new(rootNode.State.State, config.MaxStepCount);
var random = rootNode.State.State.Input.Random; 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) if (token.IsCancellationRequested)
break; break;
@@ -264,43 +267,48 @@ public sealed class Solver
} }
} }
public static (List<ActionType> Actions, SimulationState State) SearchStepwiseForked(SolverConfig config, int forkCount, SimulationInput input, Action<ActionType>? actionCallback, CancellationToken token = default) => public static (List<ActionType> Actions, SimulationState State) SearchStepwiseForked(SolverConfig config, SimulationInput input, Action<ActionType>? actionCallback, CancellationToken token = default) =>
SearchStepwiseForked(config, forkCount, new SimulationState(input), actionCallback, token); SearchStepwiseForked(config, new SimulationState(input), actionCallback, token);
public static (List<ActionType> Actions, SimulationState State) SearchStepwiseForked(SolverConfig config, int forkCount, SimulationState state, Action<ActionType>? actionCallback, CancellationToken token = default) public static (List<ActionType> Actions, SimulationState State) SearchStepwiseForked(SolverConfig config, SimulationState state, Action<ActionType>? actionCallback, CancellationToken token = default)
{ {
var actions = new List<ActionType>(); var actions = new List<ActionType>();
var sim = new Simulator(state, config.MaxStepCount); var sim = new Simulator(state, config.MaxStepCount);
while (!sim.IsComplete) while (true)
{ {
if (token.IsCancellationRequested) if (token.IsCancellationRequested)
break; break;
var tasks = new Task<(float score, List<ActionType> actions, SimulationState state)>[forkCount]; if (sim.IsComplete)
for (var i = 0; i < forkCount; ++i) break;
var s = Stopwatch.StartNew();
var tasks = new Task<(float MaxScore, (List<ActionType> Actions, SimulationNode Node) Solution)>[config.ForkCount];
for (var i = 0; i < config.ForkCount; ++i)
tasks[i] = Task.Run(() => tasks[i] = Task.Run(() =>
{ {
var solver = new Solver(config, state, true); var solver = new Solver(config, state);
solver.Search(token); solver.Search(token, config.Iterations / config.ForkCount);
var (solution_actions, solution_node) = solver.Solution(); return (solver.MaxScore, solver.Solution());
return (solver.MaxScore, solution_actions, solution_node.State);
}, token); }, token);
Task.WaitAll(tasks, CancellationToken.None); 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); actions.AddRange(solutionActions);
return (actions, solution_state); 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); (_, state) = sim.Execute(state, chosen_action);
actions.Add(chosen_action); actions.Add(chosen_action);
actionCallback?.Invoke(chosen_action);
} }
return (actions, state); return (actions, state);
@@ -313,40 +321,65 @@ public sealed class Solver
{ {
var actions = new List<ActionType>(); var actions = new List<ActionType>();
var sim = new Simulator(state, config.MaxStepCount); var sim = new Simulator(state, config.MaxStepCount);
var solver = new Solver(config, state, true); while (true)
while (!sim.IsComplete)
{ {
if (token.IsCancellationRequested) if (token.IsCancellationRequested)
break; 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(); var (solution_actions, solution_node) = solver.Solution();
if (solver.MaxScore >= 1.0) if (solver.MaxScore >= config.ScoreStorageThreshold)
{ {
actions.AddRange(solution_actions); actions.AddRange(solution_actions);
return (actions, solution_node.State); return (actions, solution_node.State);
} }
var chosen_action = solution_actions[0]; 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); (_, state) = sim.Execute(state, chosen_action);
actions.Add(chosen_action); actions.Add(chosen_action);
actionCallback?.Invoke(chosen_action);
solver = new Solver(config, state, true);
} }
return (actions, state); return (actions, state);
} }
public static (List<ActionType> Actions, SimulationState State) SearchOneshotForked(SolverConfig config, SimulationInput input, CancellationToken token = default) =>
SearchOneshotForked(config, new SimulationState(input), token);
public static (List<ActionType> Actions, SimulationState State) SearchOneshotForked(SolverConfig config, SimulationState state, CancellationToken token = default)
{
var tasks = new Task<(float MaxScore, (List<ActionType> 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<ActionType> Actions, SimulationState State) SearchOneshot(SolverConfig config, SimulationInput input, CancellationToken token = default) => public static (List<ActionType> Actions, SimulationState State) SearchOneshot(SolverConfig config, SimulationInput input, CancellationToken token = default) =>
SearchOneshot(config, new SimulationState(input), token); SearchOneshot(config, new SimulationState(input), token);
public static (List<ActionType> Actions, SimulationState State) SearchOneshot(SolverConfig config, SimulationState state, CancellationToken token = default) public static (List<ActionType> Actions, SimulationState State) SearchOneshot(SolverConfig config, SimulationState state, CancellationToken token = default)
{ {
var solver = new Solver(config, state, false); var solver = new Solver(config, state);
solver.Search(token); solver.Search(token, config.Iterations);
var (solution_actions, solution_node) = solver.Solution(); var (solution_actions, solution_node) = solver.Solution();
return (solution_actions, solution_node.State); return (solution_actions, solution_node.State);
} }
+4 -2
View File
@@ -11,7 +11,8 @@ public readonly record struct SolverConfig
public float ExplorationConstant { get; init; } public float ExplorationConstant { get; init; }
public int MaxStepCount { get; init; } public int MaxStepCount { get; init; }
public int MaxRolloutStepCount { get; init; } public int MaxRolloutStepCount { get; init; }
public int ThreadCount { get; init; } public int ForkCount { get; init; }
public bool StrictActions { get; init; }
public SolverConfig() public SolverConfig()
{ {
@@ -21,6 +22,7 @@ public readonly record struct SolverConfig
ExplorationConstant = 4; ExplorationConstant = 4;
MaxStepCount = 25; MaxStepCount = 25;
MaxRolloutStepCount = MaxStepCount; MaxRolloutStepCount = MaxStepCount;
ThreadCount = Environment.ProcessorCount; ForkCount = Environment.ProcessorCount;
StrictActions = true;
} }
} }