Add furcated actions (better results than single thread & faster!)
This commit is contained in:
+66
-21
@@ -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<SimulationState> 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<int> 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<int> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
+76
-5
@@ -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<Craftimizer.Solver.Crafty.SimulationNode>;
|
||||
|
||||
namespace Craftimizer.Solver.Crafty;
|
||||
@@ -267,10 +268,80 @@ public sealed class Solver
|
||||
}
|
||||
}
|
||||
|
||||
public static (List<ActionType> Actions, SimulationState State) SearchStepwiseForked(SolverConfig config, SimulationInput input, Action<ActionType>? actionCallback, CancellationToken token = default) =>
|
||||
public static (List<ActionType> Actions, SimulationState State) SearchStepwiseFurcated(SolverConfig config, SimulationInput input, CancellationToken token = default) =>
|
||||
SearchStepwiseFurcated(config, new SimulationState(input), token);
|
||||
|
||||
public static (List<ActionType> Actions, SimulationState State) SearchStepwiseFurcated(SolverConfig config, SimulationState state, CancellationToken token = default)
|
||||
{
|
||||
var bestSims = new List<(float Score, (List<ActionType> Actions, SimulationState State) Result)>();
|
||||
|
||||
var sim = new Simulator(state, config.MaxStepCount);
|
||||
|
||||
var activeStates = new List<(List<ActionType> Actions, SimulationState State)>() { (new(), state) };
|
||||
|
||||
while (activeStates.Count != 0)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
var s = Stopwatch.StartNew();
|
||||
var tasks = new List<Task<(float MaxScore, int FurcatedActionIdx, (List<ActionType> 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<ActionType> 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<ActionType>(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<ActionType> Actions, SimulationState State) SearchStepwiseForked(SolverConfig config, SimulationInput input, Action<ActionType>? actionCallback = null, CancellationToken token = default) =>
|
||||
SearchStepwiseForked(config, new SimulationState(input), actionCallback, token);
|
||||
|
||||
public static (List<ActionType> Actions, SimulationState State) SearchStepwiseForked(SolverConfig config, SimulationState state, Action<ActionType>? actionCallback, CancellationToken token = default)
|
||||
public static (List<ActionType> Actions, SimulationState State) SearchStepwiseForked(SolverConfig config, SimulationState state, Action<ActionType>? actionCallback = null, CancellationToken token = default)
|
||||
{
|
||||
var actions = new List<ActionType>();
|
||||
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<ActionType> Actions, SimulationState State) SearchStepwise(SolverConfig config, SimulationInput input, Action<ActionType>? actionCallback, CancellationToken token = default) =>
|
||||
public static (List<ActionType> Actions, SimulationState State) SearchStepwise(SolverConfig config, SimulationInput input, Action<ActionType>? actionCallback = null, CancellationToken token = default) =>
|
||||
SearchStepwise(config, new SimulationState(input), actionCallback, token);
|
||||
|
||||
public static (List<ActionType> Actions, SimulationState State) SearchStepwise(SolverConfig config, SimulationState state, Action<ActionType>? actionCallback, CancellationToken token = default)
|
||||
public static (List<ActionType> Actions, SimulationState State) SearchStepwise(SolverConfig config, SimulationState state, Action<ActionType>? actionCallback = null, CancellationToken token = default)
|
||||
{
|
||||
var actions = new List<ActionType>();
|
||||
var sim = new Simulator(state, config.MaxStepCount);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user