Merge branch 'offload-bufs' into main
This commit is contained in:
+94
-20
@@ -1,8 +1,6 @@
|
|||||||
using BenchmarkDotNet.Running;
|
|
||||||
using Craftimizer.Simulator;
|
using Craftimizer.Simulator;
|
||||||
using Craftimizer.Simulator.Actions;
|
using Craftimizer.Simulator.Actions;
|
||||||
using Craftimizer.Solver.Crafty;
|
using Craftimizer.Solver.Crafty;
|
||||||
using ObjectLayoutInspector;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Craftimizer.Benchmark;
|
namespace Craftimizer.Benchmark;
|
||||||
@@ -18,14 +16,15 @@ internal static class Program
|
|||||||
//return;
|
//return;
|
||||||
|
|
||||||
var input = new SimulationInput(
|
var input = new SimulationInput(
|
||||||
new CharacterStats {
|
new CharacterStats
|
||||||
Craftsmanship = 4041,
|
{
|
||||||
Control = 3905,
|
Craftsmanship = 4078,
|
||||||
CP = 609,
|
Control = 3897,
|
||||||
|
CP = 704,
|
||||||
Level = 90,
|
Level = 90,
|
||||||
CanUseManipulation = true,
|
CanUseManipulation = true,
|
||||||
HasSplendorousBuff = true,
|
HasSplendorousBuff = false,
|
||||||
IsSpecialist = true,
|
IsSpecialist = false,
|
||||||
CLvl = 560,
|
CLvl = 560,
|
||||||
},
|
},
|
||||||
new RecipeInfo()
|
new RecipeInfo()
|
||||||
@@ -46,23 +45,98 @@ internal static class Program
|
|||||||
|
|
||||||
var config = new SolverConfig()
|
var config = new SolverConfig()
|
||||||
{
|
{
|
||||||
Iterations = 1_000_000,
|
Iterations = 100_000,
|
||||||
ThreadCount = 8,
|
ForkCount = 32,
|
||||||
|
FurcatedActionCount = 16,
|
||||||
|
MaxStepCount = 30,
|
||||||
};
|
};
|
||||||
|
|
||||||
Debugger.Break();
|
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);
|
||||||
|
|
||||||
|
Console.WriteLine($"{state.Quality} {state.CP} {state.Progress} {state.Durability}");
|
||||||
|
//return;
|
||||||
|
var (_, s) = Solver.Crafty.Solver.SearchStepwiseFurcated(config, state, a => Console.WriteLine(a));
|
||||||
|
Console.WriteLine($"Qual: {s.Quality}/{s.Input.Recipe.MaxQuality}");
|
||||||
|
return;
|
||||||
|
|
||||||
|
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();
|
var s = Stopwatch.StartNew();
|
||||||
if (true)
|
List<int> q = new();
|
||||||
_ = SolverUtils.SearchStepwise<SolverSingle>(config, input, a => Console.WriteLine(a));
|
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
|
else
|
||||||
{
|
{
|
||||||
(var actions, _) = SolverUtils.SearchOneshot<SolverConcurrent>(config, input);
|
leftNumber = input[0]; // first data
|
||||||
foreach (var action in actions)
|
rightNumber = input[1]; // first data
|
||||||
Console.Write($">{action.IntName()}");
|
}
|
||||||
Console.WriteLine();
|
|
||||||
|
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,11 @@ public sealed partial class SimulatorWindow : Window, IDisposable
|
|||||||
|
|
||||||
static SimulatorWindow()
|
static SimulatorWindow()
|
||||||
{
|
{
|
||||||
SortedActions = Enum.GetValues<ActionType>().GroupBy(a => a.Category()).Select(g => (g.Key, g.OrderBy(a => a.Level()).ToArray())).ToArray();
|
SortedActions = Enum.GetValues<ActionType>()
|
||||||
|
.Where(a => a.Category() != ActionCategory.Combo)
|
||||||
|
.GroupBy(a => a.Category())
|
||||||
|
.Select(g => (g.Key, g.OrderBy(a => a.Level()).ToArray()))
|
||||||
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public enum ActionCategory
|
|||||||
Quality,
|
Quality,
|
||||||
Durability,
|
Durability,
|
||||||
Buffs,
|
Buffs,
|
||||||
|
Combo,
|
||||||
Other
|
Other
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ public enum ActionType : byte
|
|||||||
Veneration,
|
Veneration,
|
||||||
WasteNot,
|
WasteNot,
|
||||||
WasteNot2,
|
WasteNot2,
|
||||||
|
|
||||||
|
StandardTouchCombo,
|
||||||
|
AdvancedTouchCombo,
|
||||||
|
FocusedSynthesisCombo,
|
||||||
|
FocusedTouchCombo,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ActionUtils
|
public static class ActionUtils
|
||||||
@@ -106,6 +111,10 @@ public static class ActionUtils
|
|||||||
ActionType.Veneration => "Veneration",
|
ActionType.Veneration => "Veneration",
|
||||||
ActionType.WasteNot => "Waste Not",
|
ActionType.WasteNot => "Waste Not",
|
||||||
ActionType.WasteNot2 => "Waste Not II",
|
ActionType.WasteNot2 => "Waste Not II",
|
||||||
|
ActionType.StandardTouchCombo => "Standard Touch Combo",
|
||||||
|
ActionType.AdvancedTouchCombo => "Advanced Touch Combo",
|
||||||
|
ActionType.FocusedSynthesisCombo => "Focused Synthesis Combo",
|
||||||
|
ActionType.FocusedTouchCombo => "Focused Touch Combo",
|
||||||
_ => me.ToString(),
|
_ => me.ToString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
namespace Craftimizer.Simulator.Actions;
|
||||||
|
|
||||||
|
// Basic Touch -> Standard Touch -> Advanced Touch
|
||||||
|
internal sealed class AdvancedTouchCombo : BaseAction
|
||||||
|
{
|
||||||
|
public override ActionCategory Category => ActionCategory.Combo;
|
||||||
|
public override int Level => 84;
|
||||||
|
public override uint ActionId => 100411;
|
||||||
|
|
||||||
|
public override bool IncreasesQuality => true;
|
||||||
|
|
||||||
|
public override int CPCost(Simulator s) => 18 + 18 + 18;
|
||||||
|
|
||||||
|
public override bool CanUse(Simulator s) =>
|
||||||
|
// BasicTouch.DurabilityCost vv vv StandardTouch.DurabilityCost
|
||||||
|
base.CanUse(s) && VerifyDurability3(s, 10, 10);
|
||||||
|
|
||||||
|
private static readonly BasicTouch ActionA = new();
|
||||||
|
private static readonly StandardTouch ActionB = new();
|
||||||
|
private static readonly AdvancedTouch ActionC = new();
|
||||||
|
public override void Use(Simulator s)
|
||||||
|
{
|
||||||
|
s.ExecuteForced(ActionType.BasicTouch, ActionA);
|
||||||
|
s.ExecuteForced(ActionType.StandardTouch, ActionB);
|
||||||
|
ActionC.Use(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetTooltip(Simulator s, bool addUsability) =>
|
||||||
|
$"{ActionA.GetTooltip(s, addUsability)}\n{ActionB.GetTooltip(s, addUsability)}\n{ActionC.GetTooltip(s, addUsability)}";
|
||||||
|
}
|
||||||
@@ -80,4 +80,54 @@ public abstract class BaseAction
|
|||||||
builder.AppendLine($"{s.CalculateSuccessRate(SuccessRate(s)) * 100:##}%% Success Rate");
|
builder.AppendLine($"{s.CalculateSuccessRate(SuccessRate(s)) * 100:##}%% Success Rate");
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool VerifyDurability2(int durabilityA, int durability, Effects effects)
|
||||||
|
{
|
||||||
|
var wasteNots = effects.HasEffect(EffectType.WasteNot) || effects.HasEffect(EffectType.WasteNot2);
|
||||||
|
// -A
|
||||||
|
durability -= (int)MathF.Ceiling(durabilityA * (wasteNots ? .5f : 1f));
|
||||||
|
if (durability <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// If we can do the first action and still have durability left to survive to the next
|
||||||
|
// step (even before the Manipulation modifier), we can certainly do the next action.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool VerifyDurability2(SimulationState s, int durabilityA) =>
|
||||||
|
VerifyDurability2(durabilityA, s.Durability, s.ActiveEffects);
|
||||||
|
|
||||||
|
public static bool VerifyDurability2(Simulator s, int durabilityA) =>
|
||||||
|
VerifyDurability2(durabilityA, s.Durability, s.ActiveEffects);
|
||||||
|
|
||||||
|
public static bool VerifyDurability3(int durabilityA, int durabilityB, int durability, Effects effects)
|
||||||
|
{
|
||||||
|
var wasteNots = Math.Max(effects.GetDuration(EffectType.WasteNot), effects.GetDuration(EffectType.WasteNot2));
|
||||||
|
var manips = effects.HasEffect(EffectType.Manipulation);
|
||||||
|
|
||||||
|
durability -= (int)MathF.Ceiling(durabilityA * wasteNots > 0 ? .5f : 1f);
|
||||||
|
if (durability <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (manips)
|
||||||
|
durability += 5;
|
||||||
|
|
||||||
|
if (wasteNots > 0)
|
||||||
|
wasteNots--;
|
||||||
|
|
||||||
|
durability -= (int)MathF.Ceiling(durabilityB * wasteNots > 0 ? .5f : 1f);
|
||||||
|
|
||||||
|
if (durability <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// If we can do the second action and still have durability left to survive to the next
|
||||||
|
// step (even before the Manipulation modifier), we can certainly do the next action.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool VerifyDurability3(Simulator s, int durabilityA, int durabilityB) =>
|
||||||
|
VerifyDurability3(durabilityA, durabilityB, s.Durability, s.ActiveEffects);
|
||||||
|
|
||||||
|
public static bool VerifyDurability3(SimulationState s, int durabilityA, int durabilityB) =>
|
||||||
|
VerifyDurability3(durabilityA, durabilityB, s.Durability, s.ActiveEffects);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
namespace Craftimizer.Simulator.Actions;
|
||||||
|
|
||||||
|
// Observe -> Focused Synthesis
|
||||||
|
internal sealed class FocusedSynthesisCombo : BaseAction
|
||||||
|
{
|
||||||
|
public override ActionCategory Category => ActionCategory.Combo;
|
||||||
|
public override int Level => 67;
|
||||||
|
public override uint ActionId => 100235;
|
||||||
|
|
||||||
|
public override bool IncreasesProgress => true;
|
||||||
|
|
||||||
|
public override int CPCost(Simulator s) => 7 + 5;
|
||||||
|
|
||||||
|
public override bool CanUse(Simulator s) =>
|
||||||
|
// Observe.DurabilityCost v
|
||||||
|
base.CanUse(s) && VerifyDurability2(s, 0);
|
||||||
|
|
||||||
|
private static readonly Observe ActionA = new();
|
||||||
|
private static readonly FocusedSynthesis ActionB = new();
|
||||||
|
public override void Use(Simulator s)
|
||||||
|
{
|
||||||
|
s.ExecuteForced(ActionType.Observe, ActionA);
|
||||||
|
ActionB.Use(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetTooltip(Simulator s, bool addUsability) =>
|
||||||
|
$"{ActionA.GetTooltip(s, addUsability)}\n{ActionB.GetTooltip(s, addUsability)}";
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
namespace Craftimizer.Simulator.Actions;
|
||||||
|
|
||||||
|
// Observe -> Focused Touch
|
||||||
|
internal sealed class FocusedTouchCombo : BaseAction
|
||||||
|
{
|
||||||
|
public override ActionCategory Category => ActionCategory.Combo;
|
||||||
|
public override int Level => 68;
|
||||||
|
public override uint ActionId => 100243;
|
||||||
|
|
||||||
|
public override bool IncreasesQuality => true;
|
||||||
|
|
||||||
|
public override int CPCost(Simulator s) => 7 + 18;
|
||||||
|
|
||||||
|
public override bool CanUse(Simulator s) =>
|
||||||
|
// Observe.DurabilityCost v
|
||||||
|
base.CanUse(s) && VerifyDurability2(s, 0);
|
||||||
|
|
||||||
|
private static readonly Observe ActionA = new();
|
||||||
|
private static readonly FocusedTouch ActionB = new();
|
||||||
|
public override void Use(Simulator s)
|
||||||
|
{
|
||||||
|
s.ExecuteForced(ActionType.Observe, ActionA);
|
||||||
|
ActionB.Use(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetTooltip(Simulator s, bool addUsability) =>
|
||||||
|
$"{ActionA.GetTooltip(s, addUsability)}\n{ActionB.GetTooltip(s, addUsability)}";
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
namespace Craftimizer.Simulator.Actions;
|
||||||
|
|
||||||
|
// Basic Touch -> Standard Touch
|
||||||
|
internal sealed class StandardTouchCombo : BaseAction
|
||||||
|
{
|
||||||
|
public override ActionCategory Category => ActionCategory.Combo;
|
||||||
|
public override int Level => 18;
|
||||||
|
public override uint ActionId => 100004;
|
||||||
|
|
||||||
|
public override bool IncreasesQuality => true;
|
||||||
|
|
||||||
|
public override int CPCost(Simulator s) => 18 + 18;
|
||||||
|
|
||||||
|
public override bool CanUse(Simulator s) =>
|
||||||
|
// BasicTouch.DurabilityCost vv
|
||||||
|
base.CanUse(s) && VerifyDurability2(s, 10);
|
||||||
|
|
||||||
|
private static readonly BasicTouch ActionA = new();
|
||||||
|
private static readonly StandardTouch ActionB = new();
|
||||||
|
public override void Use(Simulator s)
|
||||||
|
{
|
||||||
|
s.ExecuteForced(ActionType.BasicTouch, ActionA);
|
||||||
|
ActionB.Use(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetTooltip(Simulator s, bool addUsability) =>
|
||||||
|
$"{ActionA.GetTooltip(s, addUsability)}\n{ActionB.GetTooltip(s, addUsability)}";
|
||||||
|
}
|
||||||
@@ -57,13 +57,18 @@ public class Simulator
|
|||||||
return ActionResponse.CannotUseAction;
|
return ActionResponse.CannotUseAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExecuteForced(action, baseAction);
|
||||||
|
|
||||||
|
return ActionResponse.UsedAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExecuteForced(ActionType action, BaseAction baseAction)
|
||||||
|
{
|
||||||
baseAction.Use(this);
|
baseAction.Use(this);
|
||||||
ActionStates.MutateState(action);
|
ActionStates.MutateState(action);
|
||||||
ActionCount++;
|
ActionCount++;
|
||||||
|
|
||||||
ActiveEffects.DecrementDuration();
|
ActiveEffects.DecrementDuration();
|
||||||
|
|
||||||
return ActionResponse.UsedAction;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetEffectStrength(EffectType effect) =>
|
public int GetEffectStrength(EffectType effect) =>
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ namespace Craftimizer.Solver.Crafty;
|
|||||||
|
|
||||||
public struct ActionSet
|
public struct ActionSet
|
||||||
{
|
{
|
||||||
|
private const bool IsDeterministic = false;
|
||||||
|
|
||||||
private uint bits;
|
private uint bits;
|
||||||
|
|
||||||
[Pure]
|
[Pure]
|
||||||
@@ -19,24 +21,6 @@ public struct ActionSet
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static uint ToMask(ActionType action) => 1u << FromAction(action) + 1;
|
private static uint ToMask(ActionType action) => 1u << FromAction(action) + 1;
|
||||||
|
|
||||||
// Return true if action was newly added and not there before.
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public bool AddActionConcurrent(ActionType action)
|
|
||||||
{
|
|
||||||
var mask = ToMask(action);
|
|
||||||
var old = Interlocked.Or(ref bits, mask);
|
|
||||||
return (old & mask) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true if action was newly removed and not already gone.
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public bool RemoveActionConcurrent(ActionType action)
|
|
||||||
{
|
|
||||||
var mask = ToMask(action);
|
|
||||||
var old = Interlocked.And(ref bits, ~mask);
|
|
||||||
return (old & mask) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true if action was newly added and not there before.
|
// Return true if action was newly added and not there before.
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool AddAction(ActionType action)
|
public bool AddAction(ActionType action)
|
||||||
@@ -71,52 +55,17 @@ public struct ActionSet
|
|||||||
public readonly bool IsEmpty => bits == 0;
|
public readonly bool IsEmpty => bits == 0;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly ActionType SelectRandom(Random random) => ElementAt(random.Next(Count));
|
public readonly ActionType SelectRandom(Random random) =>
|
||||||
|
IsDeterministic ?
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
First() :
|
||||||
public ActionType? PopRandomConcurrent(Random random)
|
ElementAt(random.Next(Count));
|
||||||
{
|
|
||||||
uint snapshot;
|
|
||||||
uint newValue;
|
|
||||||
ActionType action;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
snapshot = bits;
|
|
||||||
if (snapshot == 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var count = BitOperations.PopCount(snapshot);
|
|
||||||
var index = random.Next(count);
|
|
||||||
|
|
||||||
action = ToAction(Intrinsics.NthBitSet(snapshot, index) - 1);
|
|
||||||
newValue = snapshot & ~ToMask(action);
|
|
||||||
}
|
|
||||||
while (Interlocked.CompareExchange(ref bits, newValue, snapshot) != snapshot);
|
|
||||||
return action;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public ActionType? PopFirstConcurrent()
|
|
||||||
{
|
|
||||||
uint snapshot;
|
|
||||||
uint newValue;
|
|
||||||
ActionType action;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
snapshot = bits;
|
|
||||||
if (snapshot == 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
action = ToAction(Intrinsics.NthBitSet(snapshot, 0) - 1);
|
|
||||||
newValue = snapshot & ~ToMask(action);
|
|
||||||
}
|
|
||||||
while (Interlocked.CompareExchange(ref bits, newValue, snapshot) != snapshot);
|
|
||||||
return action;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public ActionType PopRandom(Random random)
|
public ActionType PopRandom(Random random)
|
||||||
{
|
{
|
||||||
|
if (IsDeterministic)
|
||||||
|
return PopFirst();
|
||||||
|
|
||||||
var action = ElementAt(random.Next(Count));
|
var action = ElementAt(random.Next(Count));
|
||||||
RemoveAction(action);
|
RemoveAction(action);
|
||||||
return action;
|
return action;
|
||||||
|
|||||||
@@ -5,51 +5,32 @@ using System.Runtime.CompilerServices;
|
|||||||
namespace Craftimizer.Solver.Crafty;
|
namespace Craftimizer.Solver.Crafty;
|
||||||
|
|
||||||
// Adapted from https://github.com/dtao/ConcurrentList/blob/4fcf1c76e93021a41af5abb2d61a63caeba2adad/ConcurrentList/ConcurrentList.cs
|
// Adapted from https://github.com/dtao/ConcurrentList/blob/4fcf1c76e93021a41af5abb2d61a63caeba2adad/ConcurrentList/ConcurrentList.cs
|
||||||
public struct ArenaBuffer<T>
|
public struct ArenaBuffer<T> where T : struct
|
||||||
{
|
{
|
||||||
// Technically 25, but it's very unlikely to actually get to there.
|
// Technically 25, but it's very unlikely to actually get to there.
|
||||||
// The benchmark reaches 20 at most, but here we have a little leeway just in case.
|
// The benchmark reaches 20 at most, but here we have a little leeway just in case.
|
||||||
private const int MaxSize = 24;
|
private const int MaxSize = 24;
|
||||||
|
|
||||||
private static int BatchSize = Vector<float>.Count;
|
private static readonly int BatchSize = Vector<float>.Count;
|
||||||
private static int BatchSizeBits = int.Log2(BatchSize);
|
private static readonly int BatchSizeBits = int.Log2(BatchSize);
|
||||||
private static int BatchSizeMask = BatchSize - 1;
|
private static readonly int BatchSizeMask = BatchSize - 1;
|
||||||
|
|
||||||
private static int BatchCount = MaxSize / BatchSize;
|
private static readonly int BatchCount = MaxSize / BatchSize;
|
||||||
|
|
||||||
public T[][] Data;
|
public ArenaNode<T>[][] Data;
|
||||||
private int index; // Unused in single threaded workload
|
public int Count { get; private set; }
|
||||||
private int count;
|
|
||||||
|
|
||||||
public readonly int Count => count;
|
public void Add(ArenaNode<T> node)
|
||||||
|
|
||||||
public void AddConcurrent(T node)
|
|
||||||
{
|
{
|
||||||
if (Data == null)
|
Data ??= new ArenaNode<T>[BatchCount][];
|
||||||
Interlocked.CompareExchange(ref Data, new T[BatchCount][], null);
|
|
||||||
|
|
||||||
var idx = Interlocked.Increment(ref index) - 1;
|
var idx = Count++;
|
||||||
|
|
||||||
var (arrayIdx, subIdx) = GetArrayIndex(idx);
|
var (arrayIdx, subIdx) = GetArrayIndex(idx);
|
||||||
|
|
||||||
if (Data[arrayIdx] == null)
|
Data[arrayIdx] ??= new ArenaNode<T>[BatchSize];
|
||||||
Interlocked.CompareExchange(ref Data[arrayIdx], new T[BatchSize], null);
|
|
||||||
|
|
||||||
Data[arrayIdx][subIdx] = node;
|
|
||||||
|
|
||||||
Interlocked.Increment(ref count);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(T node)
|
|
||||||
{
|
|
||||||
Data ??= new T[BatchCount][];
|
|
||||||
|
|
||||||
var idx = count++;
|
|
||||||
|
|
||||||
var (arrayIdx, subIdx) = GetArrayIndex(idx);
|
|
||||||
|
|
||||||
Data[arrayIdx] ??= new T[BatchSize];
|
|
||||||
|
|
||||||
|
node.ChildIdx = (arrayIdx, subIdx);
|
||||||
Data[arrayIdx][subIdx] = node;
|
Data[arrayIdx][subIdx] = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,28 +5,29 @@ namespace Craftimizer.Solver.Crafty;
|
|||||||
public sealed class ArenaNode<T> where T : struct
|
public sealed class ArenaNode<T> where T : struct
|
||||||
{
|
{
|
||||||
public T State;
|
public T State;
|
||||||
public ArenaBuffer<ArenaNode<T>> Children;
|
public ArenaBuffer<T> Children;
|
||||||
|
public NodeScoresBuffer ChildScores;
|
||||||
|
public (int arrayIdx, int subIdx) ChildIdx;
|
||||||
public readonly ArenaNode<T>? Parent;
|
public readonly ArenaNode<T>? Parent;
|
||||||
|
|
||||||
|
public NodeScoresBuffer? ParentScores => Parent?.ChildScores;
|
||||||
|
|
||||||
public ArenaNode(T state, ArenaNode<T>? parent = null)
|
public ArenaNode(T state, ArenaNode<T>? parent = null)
|
||||||
{
|
{
|
||||||
State = state;
|
State = state;
|
||||||
Children = new();
|
Children = new();
|
||||||
|
ChildScores = new();
|
||||||
Parent = parent;
|
Parent = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
public ArenaNode<T>? ChildAt((int arrayIdx, int subIdx) at) =>
|
||||||
public ArenaNode<T> AddConcurrent(T state)
|
Children.Data?[at.arrayIdx]?[at.subIdx];
|
||||||
{
|
|
||||||
var node = new ArenaNode<T>(state, this);
|
|
||||||
Children.AddConcurrent(node);
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public ArenaNode<T> Add(T state)
|
public ArenaNode<T> Add(T state)
|
||||||
{
|
{
|
||||||
var node = new ArenaNode<T>(state, this);
|
var node = new ArenaNode<T>(state, this);
|
||||||
|
ChildScores.Add();
|
||||||
Children.Add(node);
|
Children.Add(node);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
using Node = Craftimizer.Solver.Crafty.ArenaNode<Craftimizer.Solver.Crafty.SimulationNode>;
|
|
||||||
|
|
||||||
namespace Craftimizer.Solver.Crafty;
|
|
||||||
|
|
||||||
public interface ISolver
|
|
||||||
{
|
|
||||||
abstract static void LoadChildData(Span<float> scoreSums, Span<int> visits, Span<float> maxScores, ref Node[] chunk, int iterCount);
|
|
||||||
|
|
||||||
abstract static bool SearchIter(ref SolverConfig config, Node rootNode, Random random, Simulator simulator);
|
|
||||||
|
|
||||||
abstract static void Search(ref SolverConfig config, Node rootNode, CancellationToken token);
|
|
||||||
}
|
|
||||||
@@ -101,9 +101,8 @@ internal static class Intrinsics
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static int NthBitSet(uint value, int n)
|
public static int NthBitSet(uint value, int n)
|
||||||
{
|
{
|
||||||
// TODO: debug
|
|
||||||
if (n >= BitOperations.PopCount(value))
|
if (n >= BitOperations.PopCount(value))
|
||||||
throw new ArgumentException(null, nameof(value));
|
return 32;
|
||||||
|
|
||||||
return Bmi2.IsSupported ?
|
return Bmi2.IsSupported ?
|
||||||
NthBitSetBMI2(value, n) :
|
NthBitSetBMI2(value, n) :
|
||||||
@@ -125,28 +124,4 @@ internal static class Intrinsics
|
|||||||
result[i] = MathF.ReciprocalSqrtEstimate(data[i]);
|
result[i] = MathF.ReciprocalSqrtEstimate(data[i]);
|
||||||
return new(result);
|
return new(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static void CASMax(ref float location, float newValue)
|
|
||||||
{
|
|
||||||
float snapshot;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
snapshot = location;
|
|
||||||
if (snapshot >= newValue) return;
|
|
||||||
} while (Interlocked.CompareExchange(ref location, newValue, snapshot) != snapshot);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static void CASAdd(ref float location, float value)
|
|
||||||
{
|
|
||||||
float snapshot;
|
|
||||||
float newValue;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
snapshot = location;
|
|
||||||
newValue = snapshot + value;
|
|
||||||
}
|
|
||||||
while (Interlocked.CompareExchange(ref location, newValue, snapshot) != snapshot);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
using System.Diagnostics.Contracts;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Craftimizer.Solver.Crafty;
|
||||||
|
|
||||||
|
// Adapted from https://github.com/dtao/ConcurrentList/blob/4fcf1c76e93021a41af5abb2d61a63caeba2adad/ConcurrentList/ConcurrentList.cs
|
||||||
|
public struct NodeScoresBuffer
|
||||||
|
{
|
||||||
|
public sealed class ScoresBatch
|
||||||
|
{
|
||||||
|
public Memory<float> ScoreSum;
|
||||||
|
public Memory<float> MaxScore;
|
||||||
|
public Memory<int> Visits;
|
||||||
|
|
||||||
|
public ScoresBatch()
|
||||||
|
{
|
||||||
|
ScoreSum = new float[BatchSize];
|
||||||
|
MaxScore = new float[BatchSize];
|
||||||
|
Visits = new int[BatchSize];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Technically 25, but it's very unlikely to actually get to there.
|
||||||
|
// The benchmark reaches 20 at most, but here we have a little leeway just in case.
|
||||||
|
private const int MaxSize = 24;
|
||||||
|
|
||||||
|
private static readonly int BatchSize = Vector<float>.Count;
|
||||||
|
private static readonly int BatchSizeBits = int.Log2(BatchSize);
|
||||||
|
private static readonly int BatchSizeMask = BatchSize - 1;
|
||||||
|
|
||||||
|
private static readonly int BatchCount = MaxSize / BatchSize;
|
||||||
|
|
||||||
|
public ScoresBatch[] Data;
|
||||||
|
public int Count { get; private set; }
|
||||||
|
|
||||||
|
public void Add()
|
||||||
|
{
|
||||||
|
Data ??= new ScoresBatch[BatchCount];
|
||||||
|
|
||||||
|
var idx = Count++;
|
||||||
|
|
||||||
|
var (arrayIdx, _) = GetArrayIndex(idx);
|
||||||
|
|
||||||
|
Data[arrayIdx] ??= new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly void Visit((int arrayIdx, int subIdx) at, float score)
|
||||||
|
{
|
||||||
|
Data[at.arrayIdx].ScoreSum.Span[at.subIdx] += score;
|
||||||
|
Data[at.arrayIdx].MaxScore.Span[at.subIdx] = Math.Max(Data[at.arrayIdx].MaxScore.Span[at.subIdx], score);
|
||||||
|
Data[at.arrayIdx].Visits.Span[at.subIdx]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly int GetVisits((int arrayIdx, int subIdx) at) =>
|
||||||
|
Data[at.arrayIdx].Visits.Span[at.subIdx];
|
||||||
|
|
||||||
|
[Pure]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static (int arrayIdx, int subIdx) GetArrayIndex(int idx) =>
|
||||||
|
(idx >> BatchSizeBits, idx & BatchSizeMask);
|
||||||
|
}
|
||||||
@@ -3,19 +3,12 @@ using System.Runtime.InteropServices;
|
|||||||
namespace Craftimizer.Solver.Crafty;
|
namespace Craftimizer.Solver.Crafty;
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Auto)]
|
[StructLayout(LayoutKind.Auto)]
|
||||||
public struct NodeScores
|
public sealed class RootScores
|
||||||
{
|
{
|
||||||
public float ScoreSum;
|
public float ScoreSum;
|
||||||
public float MaxScore;
|
public float MaxScore;
|
||||||
public int Visits;
|
public int Visits;
|
||||||
|
|
||||||
public void VisitConcurrent(float score)
|
|
||||||
{
|
|
||||||
Intrinsics.CASAdd(ref ScoreSum, score);
|
|
||||||
Intrinsics.CASMax(ref MaxScore, score);
|
|
||||||
Interlocked.Increment(ref Visits);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Visit(float score)
|
public void Visit(float score)
|
||||||
{
|
{
|
||||||
ScoreSum += score;
|
ScoreSum += score;
|
||||||
@@ -12,7 +12,6 @@ public struct SimulationNode
|
|||||||
public readonly CompletionState SimulationCompletionState;
|
public readonly CompletionState SimulationCompletionState;
|
||||||
|
|
||||||
public ActionSet AvailableActions;
|
public ActionSet AvailableActions;
|
||||||
public NodeScores Scores;
|
|
||||||
|
|
||||||
public readonly CompletionState CompletionState => GetCompletionState(SimulationCompletionState, AvailableActions);
|
public readonly CompletionState CompletionState => GetCompletionState(SimulationCompletionState, AvailableActions);
|
||||||
|
|
||||||
@@ -31,9 +30,18 @@ public struct SimulationNode
|
|||||||
CompletionState.NoMoreActions :
|
CompletionState.NoMoreActions :
|
||||||
simCompletionState;
|
simCompletionState;
|
||||||
|
|
||||||
public readonly float? CalculateScore(int maxStepCount) => CalculateScoreForState(State, SimulationCompletionState, maxStepCount);
|
public readonly float? CalculateScore(SolverConfig config) =>
|
||||||
|
CalculateScoreForState(State, SimulationCompletionState, config);
|
||||||
|
|
||||||
public static float? CalculateScoreForState(SimulationState state, CompletionState completionState, int maxStepCount)
|
private static bool CanByregot(SimulationState state)
|
||||||
|
{
|
||||||
|
if (state.ActiveEffects.InnerQuiet == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return BaseAction.VerifyDurability2(state, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float? CalculateScoreForState(SimulationState state, CompletionState completionState, SolverConfig config)
|
||||||
{
|
{
|
||||||
if (completionState != CompletionState.ProgressComplete)
|
if (completionState != CompletionState.ProgressComplete)
|
||||||
return null;
|
return null;
|
||||||
@@ -41,38 +49,33 @@ public struct SimulationNode
|
|||||||
static float Apply(float bonus, float value, float target) =>
|
static float Apply(float bonus, float value, float target) =>
|
||||||
bonus * Math.Min(1f, value / target);
|
bonus * Math.Min(1f, value / target);
|
||||||
|
|
||||||
var progressBonus = 0.20f;
|
|
||||||
var qualityBonus = 0.65f;
|
|
||||||
var durabilityBonus = 0.05f;
|
|
||||||
var cpBonus = 0.05f;
|
|
||||||
var fewerStepsBonus = 0.05f;
|
|
||||||
|
|
||||||
var progressScore = Apply(
|
var progressScore = Apply(
|
||||||
progressBonus,
|
config.ScoreProgressBonus,
|
||||||
state.Progress,
|
state.Progress,
|
||||||
state.Input.Recipe.MaxProgress
|
state.Input.Recipe.MaxProgress
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var byregotBonus = CanByregot(state) ? (state.ActiveEffects.InnerQuiet * .2f + 1) * state.Input.BaseQualityGain : 0;
|
||||||
var qualityScore = Apply(
|
var qualityScore = Apply(
|
||||||
qualityBonus,
|
config.ScoreQualityBonus,
|
||||||
state.Quality,
|
state.Quality + byregotBonus,
|
||||||
state.Input.Recipe.MaxQuality
|
state.Input.Recipe.MaxQuality
|
||||||
);
|
);
|
||||||
|
|
||||||
var durabilityScore = Apply(
|
var durabilityScore = Apply(
|
||||||
durabilityBonus,
|
config.ScoreDurabilityBonus,
|
||||||
state.Durability,
|
state.Durability,
|
||||||
state.Input.Recipe.MaxDurability
|
state.Input.Recipe.MaxDurability
|
||||||
);
|
);
|
||||||
|
|
||||||
var cpScore = Apply(
|
var cpScore = Apply(
|
||||||
cpBonus,
|
config.ScoreCPBonus,
|
||||||
state.CP,
|
state.CP,
|
||||||
state.Input.Stats.CP
|
state.Input.Stats.CP
|
||||||
);
|
);
|
||||||
|
|
||||||
var fewerStepsScore =
|
var fewerStepsScore =
|
||||||
fewerStepsBonus * (1f - ((float)(state.ActionCount + 1) / maxStepCount));
|
config.ScoreFewerStepsBonus * (1f - ((float)(state.ActionCount + 1) / config.MaxStepCount));
|
||||||
|
|
||||||
return progressScore + qualityScore + durabilityScore + cpScore + fewerStepsScore;
|
return progressScore + qualityScore + durabilityScore + cpScore + fewerStepsScore;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ public sealed class Simulator : SimulatorNoRandom
|
|||||||
|
|
||||||
public static readonly ActionType[] AcceptedActions = new[]
|
public static readonly ActionType[] AcceptedActions = new[]
|
||||||
{
|
{
|
||||||
|
ActionType.StandardTouchCombo,
|
||||||
|
ActionType.AdvancedTouchCombo,
|
||||||
|
ActionType.FocusedTouchCombo,
|
||||||
|
ActionType.FocusedSynthesisCombo,
|
||||||
ActionType.TrainedFinesse,
|
ActionType.TrainedFinesse,
|
||||||
ActionType.PrudentSynthesis,
|
ActionType.PrudentSynthesis,
|
||||||
ActionType.Groundwork,
|
ActionType.Groundwork,
|
||||||
@@ -94,12 +98,29 @@ public sealed class Simulator : SimulatorNoRandom
|
|||||||
baseAction.IncreasesQuality)
|
baseAction.IncreasesQuality)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// use First Turn actions if it's available and the craft is difficult
|
||||||
|
if (IsFirstStep &&
|
||||||
|
Input.Recipe.ClassJobLevel == 90 &&
|
||||||
|
baseAction.Category != ActionCategory.FirstTurn &&
|
||||||
|
CP > 10)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// don't allow combo actions if the combo is already in progress
|
||||||
|
if (ActionStates.TouchComboIdx != 0 &&
|
||||||
|
(action == ActionType.StandardTouchCombo || action == ActionType.AdvancedTouchCombo))
|
||||||
|
return false;
|
||||||
|
|
||||||
// don't allow pure quality moves under Veneration
|
// don't allow pure quality moves under Veneration
|
||||||
if (HasEffect(EffectType.Veneration) &&
|
if (HasEffect(EffectType.Veneration) &&
|
||||||
!baseAction.IncreasesProgress &&
|
!baseAction.IncreasesProgress &&
|
||||||
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));
|
||||||
@@ -130,7 +151,7 @@ public sealed class Simulator : SimulatorNoRandom
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (action == ActionType.Observe &&
|
if (action == ActionType.Observe &&
|
||||||
CP < 5)
|
CP < 12)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (action == ActionType.MastersMend &&
|
if (action == ActionType.MastersMend &&
|
||||||
|
|||||||
@@ -0,0 +1,558 @@
|
|||||||
|
using Craftimizer.Simulator;
|
||||||
|
using Craftimizer.Simulator.Actions;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.Contracts;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Node = Craftimizer.Solver.Crafty.ArenaNode<Craftimizer.Solver.Crafty.SimulationNode>;
|
||||||
|
|
||||||
|
namespace Craftimizer.Solver.Crafty;
|
||||||
|
|
||||||
|
// https://github.com/alostsock/crafty/blob/cffbd0cad8bab3cef9f52a3e3d5da4f5e3781842/crafty/src/simulator.rs
|
||||||
|
public sealed class Solver
|
||||||
|
{
|
||||||
|
private SolverConfig config;
|
||||||
|
private Node rootNode;
|
||||||
|
private RootScores rootScores;
|
||||||
|
|
||||||
|
public float MaxScore => rootScores.MaxScore;
|
||||||
|
|
||||||
|
public Solver(SolverConfig config, SimulationState state)
|
||||||
|
{
|
||||||
|
this.config = config;
|
||||||
|
var sim = new Simulator(state, config.MaxStepCount);
|
||||||
|
rootNode = new(new(
|
||||||
|
state,
|
||||||
|
null,
|
||||||
|
sim.CompletionState,
|
||||||
|
sim.AvailableActionsHeuristic(config.StrictActions)
|
||||||
|
));
|
||||||
|
rootScores = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SimulationNode Execute(Simulator simulator, SimulationState state, ActionType action, bool strict)
|
||||||
|
{
|
||||||
|
(_, var newState) = simulator.Execute(state, action);
|
||||||
|
return new(
|
||||||
|
newState,
|
||||||
|
action,
|
||||||
|
simulator.CompletionState,
|
||||||
|
simulator.AvailableActionsHeuristic(strict)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Node ExecuteActions(Simulator simulator, Node startNode, ReadOnlySpan<ActionType> actions, bool strict)
|
||||||
|
{
|
||||||
|
foreach (var action in actions)
|
||||||
|
{
|
||||||
|
var state = startNode.State;
|
||||||
|
if (state.IsComplete)
|
||||||
|
return startNode;
|
||||||
|
|
||||||
|
if (!state.AvailableActions.HasAction(action))
|
||||||
|
return startNode;
|
||||||
|
state.AvailableActions.RemoveAction(action);
|
||||||
|
|
||||||
|
startNode = startNode.Add(Execute(simulator, state.State, action, strict));
|
||||||
|
}
|
||||||
|
|
||||||
|
return startNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Pure]
|
||||||
|
private (List<ActionType> Actions, SimulationNode Node) Solution()
|
||||||
|
{
|
||||||
|
var actions = new List<ActionType>();
|
||||||
|
var node = rootNode;
|
||||||
|
|
||||||
|
while (node.Children.Count != 0)
|
||||||
|
{
|
||||||
|
node = node.ChildAt(ChildMaxScore(ref node.ChildScores))!;
|
||||||
|
|
||||||
|
if (node.State.Action != null)
|
||||||
|
actions.Add(node.State.Action.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var at = node.ChildIdx;
|
||||||
|
ref var sum = ref node.ParentScores!.Value.Data[at.arrayIdx].ScoreSum.Span[at.subIdx];
|
||||||
|
ref var max = ref node.ParentScores!.Value.Data[at.arrayIdx].MaxScore.Span[at.subIdx];
|
||||||
|
ref var visits = ref node.ParentScores!.Value.Data[at.arrayIdx].Visits.Span[at.subIdx];
|
||||||
|
//Console.WriteLine($"{sum} {max} {visits}");
|
||||||
|
|
||||||
|
return (actions, node.State);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Pure]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static (int arrayIdx, int subIdx) ChildMaxScore(ref NodeScoresBuffer scores)
|
||||||
|
{
|
||||||
|
var length = scores.Count;
|
||||||
|
var vecLength = Vector<float>.Count;
|
||||||
|
|
||||||
|
var max = (0, 0);
|
||||||
|
var maxScore = 0f;
|
||||||
|
for (var i = 0; length > 0; ++i)
|
||||||
|
{
|
||||||
|
var iterCount = Math.Min(vecLength, length);
|
||||||
|
|
||||||
|
ref var chunk = ref scores.Data[i];
|
||||||
|
var m = new Vector<float>(chunk.MaxScore.Span);
|
||||||
|
|
||||||
|
var idx = Intrinsics.HMaxIndex(m, iterCount);
|
||||||
|
|
||||||
|
if (m[idx] >= maxScore)
|
||||||
|
{
|
||||||
|
max = (i, idx);
|
||||||
|
maxScore = m[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
length -= iterCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculates the best child node to explore next
|
||||||
|
// Exploitation: ((1 - w) * (s / v)) + (w * m)
|
||||||
|
// Exploration: sqrt(c * ln(V) / v)
|
||||||
|
// w = maxScoreWeightingConstant
|
||||||
|
// s = score sum
|
||||||
|
// m = max score
|
||||||
|
// v = visits
|
||||||
|
// V = parentVisits
|
||||||
|
// c = explorationConstant
|
||||||
|
|
||||||
|
// Somewhat based off of https://en.wikipedia.org/wiki/Monte_Carlo_tree_search#Exploration_and_exploitation
|
||||||
|
// Here, w_i = (1-w)*score sum
|
||||||
|
// n_i = visits
|
||||||
|
// max score is tacked onto it
|
||||||
|
// N_i = parent visits
|
||||||
|
// c = exploration constant (but crafty places it inside the sqrt..?)
|
||||||
|
[Pure]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
private (int arrayIdx, int subIdx) EvalBestChild(int parentVisits, ref NodeScoresBuffer scores)
|
||||||
|
{
|
||||||
|
var length = scores.Count;
|
||||||
|
var vecLength = Vector<float>.Count;
|
||||||
|
|
||||||
|
var C = MathF.Sqrt(config.ExplorationConstant * MathF.Log(parentVisits));
|
||||||
|
var w = config.MaxScoreWeightingConstant;
|
||||||
|
var W = 1f - w;
|
||||||
|
var CVector = new Vector<float>(C);
|
||||||
|
|
||||||
|
var max = (0, 0);
|
||||||
|
var maxScore = 0f;
|
||||||
|
for (var i = 0; length > 0; ++i)
|
||||||
|
{
|
||||||
|
var iterCount = Math.Min(vecLength, length);
|
||||||
|
|
||||||
|
ref var chunk = ref scores.Data[i];
|
||||||
|
var s = new Vector<float>(chunk.ScoreSum.Span);
|
||||||
|
var vInt = new Vector<int>(chunk.Visits.Span);
|
||||||
|
var m = new Vector<float>(chunk.MaxScore.Span);
|
||||||
|
|
||||||
|
vInt = Vector.Max(vInt, Vector<int>.One);
|
||||||
|
var v = Vector.ConvertToSingle(vInt);
|
||||||
|
|
||||||
|
var exploitation = (W * (s / v)) + (w * m);
|
||||||
|
var exploration = CVector * Intrinsics.ReciprocalSqrt(v);
|
||||||
|
var evalScores = exploitation + exploration;
|
||||||
|
|
||||||
|
var idx = Intrinsics.HMaxIndex(evalScores, iterCount);
|
||||||
|
|
||||||
|
if (evalScores[idx] >= maxScore)
|
||||||
|
{
|
||||||
|
max = (i, idx);
|
||||||
|
maxScore = evalScores[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
length -= iterCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Pure]
|
||||||
|
public Node Select()
|
||||||
|
{
|
||||||
|
var node = rootNode;
|
||||||
|
var nodeVisits = rootScores.Visits;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var expandable = !node.State.AvailableActions.IsEmpty;
|
||||||
|
var likelyTerminal = node.Children.Count == 0;
|
||||||
|
if (expandable || likelyTerminal)
|
||||||
|
return node;
|
||||||
|
|
||||||
|
// select the node with the highest score
|
||||||
|
var at = EvalBestChild(nodeVisits, ref node.ChildScores);
|
||||||
|
nodeVisits = node.ChildScores.GetVisits(at);
|
||||||
|
node = node.ChildAt(at)!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public (Node ExpandedNode, float Score) ExpandAndRollout(Random random, Simulator simulator, Node initialNode)
|
||||||
|
{
|
||||||
|
ref var initialState = ref initialNode.State;
|
||||||
|
// expand once
|
||||||
|
if (initialState.IsComplete)
|
||||||
|
return (initialNode, initialState.CalculateScore(config) ?? 0);
|
||||||
|
|
||||||
|
var poppedAction = initialState.AvailableActions.PopRandom(random);
|
||||||
|
var expandedNode = initialNode.Add(Execute(simulator, initialState.State, poppedAction, true));
|
||||||
|
|
||||||
|
// playout to a terminal state
|
||||||
|
var currentState = expandedNode.State.State;
|
||||||
|
var currentCompletionState = expandedNode.State.SimulationCompletionState;
|
||||||
|
var currentActions = expandedNode.State.AvailableActions;
|
||||||
|
|
||||||
|
|
||||||
|
byte actionCount = 0;
|
||||||
|
Span<ActionType> actions = stackalloc ActionType[Math.Min(config.MaxStepCount - currentState.ActionCount, config.MaxRolloutStepCount)];
|
||||||
|
while (SimulationNode.GetCompletionState(currentCompletionState, currentActions) == CompletionState.Incomplete &&
|
||||||
|
actionCount < actions.Length)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// store the result if a max score was reached
|
||||||
|
var score = SimulationNode.CalculateScoreForState(currentState, currentCompletionState, config) ?? 0;
|
||||||
|
if (currentCompletionState == CompletionState.ProgressComplete)
|
||||||
|
{
|
||||||
|
if (score >= config.ScoreStorageThreshold && score >= MaxScore)
|
||||||
|
{
|
||||||
|
var terminalNode = ExecuteActions(simulator, expandedNode, actions[..actionCount], true);
|
||||||
|
return (terminalNode, score);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (expandedNode, score);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Backpropagate(Node startNode, float score)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (startNode == rootNode)
|
||||||
|
{
|
||||||
|
rootScores.Visit(score);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
startNode.ParentScores!.Value.Visit(startNode.ChildIdx, score);
|
||||||
|
|
||||||
|
startNode = startNode.Parent!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowAllNodes()
|
||||||
|
{
|
||||||
|
static void ShowNodes(StringBuilder b, Node node, Stack<Node> path)
|
||||||
|
{
|
||||||
|
path.Push(node);
|
||||||
|
b.AppendLine($"{new string(' ', path.Count)}{node.State.Action}");
|
||||||
|
{
|
||||||
|
for (var i = 0; i < node.Children.Count; ++i)
|
||||||
|
{
|
||||||
|
var n = node.ChildAt((i >> 3, i & 7))!;
|
||||||
|
ShowNodes(b, n, path);
|
||||||
|
}
|
||||||
|
path.Pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var b = new StringBuilder();
|
||||||
|
ShowNodes(b, rootNode, new());
|
||||||
|
Console.WriteLine(b.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AllNodesComplete()
|
||||||
|
{
|
||||||
|
static bool NodesIncomplete(Node node, Stack<Node> path)
|
||||||
|
{
|
||||||
|
path.Push(node);
|
||||||
|
if (node.Children.Count == 0)
|
||||||
|
{
|
||||||
|
if (!node.State.AvailableActions.IsEmpty)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for(var i = 0; i < node.Children.Count; ++i)
|
||||||
|
{
|
||||||
|
var n = node.ChildAt((i >> 3, i & 7))!;
|
||||||
|
if (NodesIncomplete(n, path))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
path.Pop();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !NodesIncomplete(rootNode, new());
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void Search(int iterations, CancellationToken token)
|
||||||
|
{
|
||||||
|
Simulator simulator = new(rootNode.State.State, config.MaxStepCount);
|
||||||
|
var random = rootNode.State.State.Input.Random;
|
||||||
|
var n = 0;
|
||||||
|
for (var i = 0; i < iterations || MaxScore == 0; i++)
|
||||||
|
{
|
||||||
|
if (token.IsCancellationRequested)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var selectedNode = Select();
|
||||||
|
var (endNode, score) = ExpandAndRollout(random, simulator, selectedNode);
|
||||||
|
if (MaxScore == 0)
|
||||||
|
{
|
||||||
|
if (endNode == selectedNode)
|
||||||
|
{
|
||||||
|
if (n++ > 5000)
|
||||||
|
{
|
||||||
|
n = 0;
|
||||||
|
if (AllNodesComplete())
|
||||||
|
{
|
||||||
|
//Console.WriteLine("All nodes solved for. Can't find a valid solution.");
|
||||||
|
//ShowAllNodes();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
n = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Backpropagate(endNode, score);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (List<ActionType> Actions, SimulationState State) SearchStepwiseFurcated(SolverConfig config, SimulationInput input, Action<ActionType>? actionCallback = null, CancellationToken token = default) =>
|
||||||
|
SearchStepwiseFurcated(config, new SimulationState(input), actionCallback, token);
|
||||||
|
|
||||||
|
public static (List<ActionType> Actions, SimulationState State) SearchStepwiseFurcated(SolverConfig config, SimulationState state, Action<ActionType>? actionCallback = null, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var definiteActionCount = 0;
|
||||||
|
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(config.Iterations / config.ForkCount, token);
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestSims.Count == 0 && newStates.Count != 0)
|
||||||
|
{
|
||||||
|
var definiteCount = definiteActionCount;
|
||||||
|
var equalCount = int.MaxValue;
|
||||||
|
var refActions = newStates[0].Actions;
|
||||||
|
for(var i = 1; i < newStates.Count; ++i)
|
||||||
|
{
|
||||||
|
var cmpActions = newStates[i].Actions;
|
||||||
|
var possibleCount = Math.Min(Math.Min(refActions.Count, cmpActions.Count), equalCount);
|
||||||
|
var completelyEqual = true;
|
||||||
|
for (var j = definiteCount; j < possibleCount; ++j)
|
||||||
|
{
|
||||||
|
if (refActions[j] != cmpActions[j])
|
||||||
|
{
|
||||||
|
equalCount = j;
|
||||||
|
completelyEqual = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (completelyEqual)
|
||||||
|
equalCount = possibleCount;
|
||||||
|
}
|
||||||
|
if (definiteCount != equalCount)
|
||||||
|
{
|
||||||
|
for (var i = definiteCount; i < equalCount; ++i)
|
||||||
|
actionCallback?.Invoke(refActions[i]);
|
||||||
|
|
||||||
|
definiteActionCount = equalCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activeStates = newStates;
|
||||||
|
|
||||||
|
Console.WriteLine($"{s.Elapsed.TotalMilliseconds:0.00}ms {config.Iterations / config.ForkCount / s.Elapsed.TotalSeconds / 1000:0.00} kI/s/t");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = bestSims.MaxBy(s => s.Score).Result;
|
||||||
|
for (var i = definiteActionCount; i < result.Actions.Count; ++i)
|
||||||
|
actionCallback?.Invoke(result.Actions[i]);
|
||||||
|
|
||||||
|
return 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 = null, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var actions = new List<ActionType>();
|
||||||
|
var sim = new Simulator(state, config.MaxStepCount);
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (token.IsCancellationRequested)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (sim.IsComplete)
|
||||||
|
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(() =>
|
||||||
|
{
|
||||||
|
var solver = new Solver(config, state);
|
||||||
|
solver.Search(config.Iterations / config.ForkCount, token);
|
||||||
|
return (solver.MaxScore, solver.Solution());
|
||||||
|
}, token);
|
||||||
|
Task.WaitAll(tasks, CancellationToken.None);
|
||||||
|
s.Stop();
|
||||||
|
|
||||||
|
var (maxScore, (solutionActions, solutionNode)) = tasks.Select(t => t.Result).MaxBy(r => r.MaxScore);
|
||||||
|
|
||||||
|
if (maxScore >= config.ScoreStorageThreshold)
|
||||||
|
{
|
||||||
|
actions.AddRange(solutionActions);
|
||||||
|
return (actions, solutionNode.State);
|
||||||
|
}
|
||||||
|
|
||||||
|
var chosen_action = solutionActions[0];
|
||||||
|
actionCallback?.Invoke(chosen_action);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (actions, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = null, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var actions = new List<ActionType>();
|
||||||
|
var sim = new Simulator(state, config.MaxStepCount);
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (token.IsCancellationRequested)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (sim.IsComplete)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var solver = new Solver(config, state);
|
||||||
|
|
||||||
|
var s = Stopwatch.StartNew();
|
||||||
|
solver.Search(config.Iterations, token);
|
||||||
|
s.Stop();
|
||||||
|
|
||||||
|
var (solution_actions, solution_node) = solver.Solution();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(config.Iterations / config.ForkCount, token);
|
||||||
|
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) =>
|
||||||
|
SearchOneshot(config, new SimulationState(input), token);
|
||||||
|
|
||||||
|
public static (List<ActionType> Actions, SimulationState State) SearchOneshot(SolverConfig config, SimulationState state, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var solver = new Solver(config, state);
|
||||||
|
solver.Search(config.Iterations, token);
|
||||||
|
var (solution_actions, solution_node) = solver.Solution();
|
||||||
|
return (solution_actions, solution_node.State);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
using System.Diagnostics.Contracts;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using Node = Craftimizer.Solver.Crafty.ArenaNode<Craftimizer.Solver.Crafty.SimulationNode>;
|
|
||||||
|
|
||||||
namespace Craftimizer.Solver.Crafty;
|
|
||||||
|
|
||||||
// https://github.com/alostsock/crafty/blob/cffbd0cad8bab3cef9f52a3e3d5da4f5e3781842/crafty/src/simulator.rs
|
|
||||||
public sealed class SolverConcurrent : ISolver
|
|
||||||
{
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
public static void LoadChildData(Span<float> scoreSums, Span<int> visits, Span<float> maxScores, ref Node[] chunk, int iterCount)
|
|
||||||
{
|
|
||||||
for (var j = 0; j < iterCount; ++j)
|
|
||||||
{
|
|
||||||
var node = chunk[j]?.State.Scores ?? new();
|
|
||||||
scoreSums[j] = node.ScoreSum;
|
|
||||||
visits[j] = node.Visits;
|
|
||||||
maxScores[j] = node.MaxScore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Pure]
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static Node? EvalBestChild(ref SolverConfig config, int parentVisits, ref ArenaBuffer<Node> children) =>
|
|
||||||
parentVisits == 0 ?
|
|
||||||
null :
|
|
||||||
SolverUtils.EvalBestChild<SolverConcurrent>(ref config, parentVisits, ref children);
|
|
||||||
|
|
||||||
[Pure]
|
|
||||||
public static Node Select(ref SolverConfig config, Node rootNode)
|
|
||||||
{
|
|
||||||
var node = rootNode;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var expandable = !node.State.AvailableActions.IsEmpty;
|
|
||||||
var likelyTerminal = node.Children.Count == 0;
|
|
||||||
if (expandable || likelyTerminal)
|
|
||||||
return node;
|
|
||||||
|
|
||||||
// select the node with the highest score
|
|
||||||
// if null (current node is invalid & not backpropagated just yet), try again from root
|
|
||||||
node = EvalBestChild(ref config, node.State.Scores.Visits, ref node.Children) ?? rootNode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static (Node ExpandedNode, float Score)? ExpandAndRollout(ref SolverConfig config, Node rootNode, Random random, Simulator simulator, Node initialNode)
|
|
||||||
{
|
|
||||||
ref var initialState = ref initialNode.State;
|
|
||||||
// expand once
|
|
||||||
if (initialState.IsComplete)
|
|
||||||
return (initialNode, initialState.CalculateScore(config.MaxStepCount) ?? 0);
|
|
||||||
|
|
||||||
var poppedAction = initialState.AvailableActions.PopRandomConcurrent(random);
|
|
||||||
if (!poppedAction.HasValue)
|
|
||||||
return null;
|
|
||||||
var expandedNode = initialNode.AddConcurrent(SolverUtils.Execute(simulator, initialState.State, poppedAction.Value, true));
|
|
||||||
|
|
||||||
return SolverUtils.Rollout(ref config, rootNode, expandedNode, random, simulator);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Backpropagate(Node rootNode, Node startNode, float score)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
startNode.State.Scores.VisitConcurrent(score);
|
|
||||||
|
|
||||||
if (startNode == rootNode)
|
|
||||||
break;
|
|
||||||
|
|
||||||
startNode = startNode.Parent!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool SearchIter(ref SolverConfig config, Node rootNode, Random random, Simulator simulator)
|
|
||||||
{
|
|
||||||
var selectedNode = Select(ref config, rootNode);
|
|
||||||
var rolledOut = ExpandAndRollout(ref config, rootNode, random, simulator, selectedNode);
|
|
||||||
if (!rolledOut.HasValue)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var (endNode, score) = rolledOut.Value;
|
|
||||||
Backpropagate(rootNode, endNode, score);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SearchThread(SolverConfig config, Node rootNode, CancellationToken token) =>
|
|
||||||
SolverUtils.Search<SolverConcurrent>(ref config, config.Iterations / config.ThreadCount, rootNode, token);
|
|
||||||
|
|
||||||
public static void Search(ref SolverConfig config, Node rootNode, CancellationToken token)
|
|
||||||
{
|
|
||||||
var configP = config;
|
|
||||||
var tasks = new Task[config.ThreadCount];
|
|
||||||
for (var i = 0; i < config.ThreadCount; ++i)
|
|
||||||
tasks[i] = Task.Run(() => SearchThread(configP, rootNode, token), token);
|
|
||||||
Task.WaitAll(tasks, CancellationToken.None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,15 +10,33 @@ public readonly record struct SolverConfig
|
|||||||
public float MaxScoreWeightingConstant { get; init; }
|
public float MaxScoreWeightingConstant { get; init; }
|
||||||
public float ExplorationConstant { get; init; }
|
public float ExplorationConstant { get; init; }
|
||||||
public int MaxStepCount { get; init; }
|
public int MaxStepCount { get; init; }
|
||||||
public int ThreadCount { get; init; }
|
public int MaxRolloutStepCount { get; init; }
|
||||||
|
public int ForkCount { get; init; }
|
||||||
|
public int FurcatedActionCount { get; init; }
|
||||||
|
public bool StrictActions { get; init; }
|
||||||
|
|
||||||
|
public float ScoreProgressBonus { get; init; }
|
||||||
|
public float ScoreQualityBonus { get; init; }
|
||||||
|
public float ScoreDurabilityBonus { get; init; }
|
||||||
|
public float ScoreCPBonus { get; init; }
|
||||||
|
public float ScoreFewerStepsBonus { get; init; }
|
||||||
|
|
||||||
public SolverConfig()
|
public SolverConfig()
|
||||||
{
|
{
|
||||||
Iterations = 300000;
|
Iterations = 300000;
|
||||||
ScoreStorageThreshold = 1f;
|
ScoreStorageThreshold = 1f;
|
||||||
MaxScoreWeightingConstant = 0.1f;
|
MaxScoreWeightingConstant = 0.1f;
|
||||||
ExplorationConstant = 4f;
|
ExplorationConstant = 4;
|
||||||
MaxStepCount = 25;
|
MaxStepCount = 25;
|
||||||
ThreadCount = Environment.ProcessorCount;
|
MaxRolloutStepCount = MaxStepCount;
|
||||||
|
ForkCount = Environment.ProcessorCount;
|
||||||
|
FurcatedActionCount = ForkCount / 2;
|
||||||
|
StrictActions = true;
|
||||||
|
|
||||||
|
ScoreProgressBonus = .20f;
|
||||||
|
ScoreQualityBonus = .65f;
|
||||||
|
ScoreDurabilityBonus = .05f;
|
||||||
|
ScoreCPBonus = .05f;
|
||||||
|
ScoreFewerStepsBonus = .05f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
using System.Diagnostics.Contracts;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using Node = Craftimizer.Solver.Crafty.ArenaNode<Craftimizer.Solver.Crafty.SimulationNode>;
|
|
||||||
|
|
||||||
namespace Craftimizer.Solver.Crafty;
|
|
||||||
|
|
||||||
// https://github.com/alostsock/crafty/blob/cffbd0cad8bab3cef9f52a3e3d5da4f5e3781842/crafty/src/simulator.rs
|
|
||||||
public sealed class SolverSingle : ISolver
|
|
||||||
{
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
public static void LoadChildData(Span<float> scoreSums, Span<int> visits, Span<float> maxScores, ref Node[] chunk, int iterCount)
|
|
||||||
{
|
|
||||||
for (var j = 0; j < iterCount; ++j)
|
|
||||||
{
|
|
||||||
ref var node = ref chunk[j].State.Scores;
|
|
||||||
scoreSums[j] = node.ScoreSum;
|
|
||||||
visits[j] = node.Visits;
|
|
||||||
maxScores[j] = node.MaxScore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Pure]
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static Node EvalBestChild(ref SolverConfig config, int parentVisits, ref ArenaBuffer<Node> children) =>
|
|
||||||
SolverUtils.EvalBestChild<SolverSingle>(ref config, parentVisits, ref children);
|
|
||||||
|
|
||||||
[Pure]
|
|
||||||
public static Node Select(ref SolverConfig config, Node node)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var expandable = !node.State.AvailableActions.IsEmpty;
|
|
||||||
var likelyTerminal = node.Children.Count == 0;
|
|
||||||
if (expandable || likelyTerminal)
|
|
||||||
return node;
|
|
||||||
|
|
||||||
// select the node with the highest score
|
|
||||||
node = EvalBestChild(ref config, node.State.Scores.Visits, ref node.Children);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static (Node ExpandedNode, float Score) ExpandAndRollout(ref SolverConfig config, Node rootNode, Random random, Simulator simulator, Node initialNode)
|
|
||||||
{
|
|
||||||
ref var initialState = ref initialNode.State;
|
|
||||||
// expand once
|
|
||||||
if (initialState.IsComplete)
|
|
||||||
return (initialNode, initialState.CalculateScore(config.MaxStepCount) ?? 0);
|
|
||||||
|
|
||||||
var poppedAction = initialState.AvailableActions.PopRandom(random);
|
|
||||||
var expandedNode = initialNode.Add(SolverUtils.Execute(simulator, initialState.State, poppedAction, true));
|
|
||||||
|
|
||||||
return SolverUtils.Rollout(ref config, rootNode, expandedNode, random, simulator);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Backpropagate(Node rootNode, Node startNode, float score)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
startNode.State.Scores.Visit(score);
|
|
||||||
|
|
||||||
if (startNode == rootNode)
|
|
||||||
break;
|
|
||||||
|
|
||||||
startNode = startNode.Parent!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool SearchIter(ref SolverConfig config, Node rootNode, Random random, Simulator simulator)
|
|
||||||
{
|
|
||||||
var selectedNode = Select(ref config, rootNode);
|
|
||||||
var (endNode, score) = ExpandAndRollout(ref config, rootNode, random, simulator, selectedNode);
|
|
||||||
|
|
||||||
Backpropagate(rootNode, endNode, score);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Search(ref SolverConfig config, Node rootNode, CancellationToken token) =>
|
|
||||||
SolverUtils.Search<SolverSingle>(ref config, config.Iterations, rootNode, token);
|
|
||||||
}
|
|
||||||
@@ -1,250 +0,0 @@
|
|||||||
using Craftimizer.Simulator.Actions;
|
|
||||||
using Craftimizer.Simulator;
|
|
||||||
using Node = Craftimizer.Solver.Crafty.ArenaNode<Craftimizer.Solver.Crafty.SimulationNode>;
|
|
||||||
using System.Diagnostics.Contracts;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace Craftimizer.Solver.Crafty;
|
|
||||||
public static class SolverUtils
|
|
||||||
{
|
|
||||||
public static SimulationNode Execute(Simulator simulator, SimulationState state, ActionType action, bool strict)
|
|
||||||
{
|
|
||||||
(_, var newState) = simulator.Execute(state, action);
|
|
||||||
return new(
|
|
||||||
newState,
|
|
||||||
action,
|
|
||||||
simulator.CompletionState,
|
|
||||||
simulator.AvailableActionsHeuristic(strict)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static (Node EndNode, CompletionState State) ExecuteActions(Simulator simulator, Node startNode, ReadOnlySpan<ActionType> actions, bool strict = false)
|
|
||||||
{
|
|
||||||
foreach (var action in actions)
|
|
||||||
{
|
|
||||||
var state = startNode.State;
|
|
||||||
if (state.IsComplete)
|
|
||||||
return (startNode, state.CompletionState);
|
|
||||||
|
|
||||||
if (!state.AvailableActions.HasAction(action))
|
|
||||||
return (startNode, CompletionState.InvalidAction);
|
|
||||||
state.AvailableActions.RemoveAction(action);
|
|
||||||
|
|
||||||
startNode = startNode.Add(Execute(simulator, state.State, action, strict));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (startNode, startNode.State.CompletionState);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Pure]
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static Node ChildMaxScore(ref ArenaBuffer<Node> children)
|
|
||||||
{
|
|
||||||
var length = children.Count;
|
|
||||||
var vecLength = Vector<float>.Count;
|
|
||||||
|
|
||||||
Span<float> scores = stackalloc float[vecLength];
|
|
||||||
|
|
||||||
var max = (0, 0);
|
|
||||||
var maxScore = 0f;
|
|
||||||
for (var i = 0; length > 0; ++i)
|
|
||||||
{
|
|
||||||
var iterCount = Math.Min(vecLength, length);
|
|
||||||
|
|
||||||
ref var chunk = ref children.Data[i];
|
|
||||||
for (var j = 0; j < iterCount; ++j)
|
|
||||||
scores[j] = chunk[j].State.Scores.MaxScore;
|
|
||||||
|
|
||||||
var idx = Intrinsics.HMaxIndex(new Vector<float>(scores), iterCount);
|
|
||||||
|
|
||||||
if (scores[idx] >= maxScore)
|
|
||||||
{
|
|
||||||
max = (i, idx);
|
|
||||||
maxScore = scores[idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
length -= iterCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
return children.Data[max.Item1][max.Item2];
|
|
||||||
}
|
|
||||||
|
|
||||||
[Pure]
|
|
||||||
public static (List<ActionType> Actions, SimulationNode Node) Solution(Node node)
|
|
||||||
{
|
|
||||||
var actions = new List<ActionType>();
|
|
||||||
while (node.Children.Count != 0)
|
|
||||||
{
|
|
||||||
node = ChildMaxScore(ref node.Children);
|
|
||||||
|
|
||||||
if (node.State.Action != null)
|
|
||||||
actions.Add(node.State.Action.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (actions, node.State);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Pure]
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
public static Node EvalBestChild<S>(ref SolverConfig config, int parentVisits, ref ArenaBuffer<Node> children) where S : ISolver
|
|
||||||
{
|
|
||||||
var length = children.Count;
|
|
||||||
var vecLength = Vector<float>.Count;
|
|
||||||
|
|
||||||
var C = MathF.Sqrt(config.ExplorationConstant * MathF.Log(parentVisits));
|
|
||||||
var w = config.MaxScoreWeightingConstant;
|
|
||||||
var W = 1f - w;
|
|
||||||
var CVector = new Vector<float>(C);
|
|
||||||
|
|
||||||
Span<float> scoreSums = stackalloc float[vecLength];
|
|
||||||
Span<int> visits = stackalloc int[vecLength];
|
|
||||||
Span<float> maxScores = stackalloc float[vecLength];
|
|
||||||
|
|
||||||
var max = (0, 0);
|
|
||||||
var maxScore = 0f;
|
|
||||||
for (var i = 0; length > 0; ++i)
|
|
||||||
{
|
|
||||||
var iterCount = Math.Min(vecLength, length);
|
|
||||||
|
|
||||||
S.LoadChildData(scoreSums, visits, maxScores, ref children.Data[i], iterCount);
|
|
||||||
|
|
||||||
var s = new Vector<float>(scoreSums);
|
|
||||||
var m = new Vector<float>(maxScores);
|
|
||||||
var vInt = new Vector<int>(visits);
|
|
||||||
vInt = Vector.Max(vInt, Vector<int>.One);
|
|
||||||
var v = Vector.ConvertToSingle(vInt);
|
|
||||||
var exploitation = (W * (s / v)) + (w * m);
|
|
||||||
var exploration = CVector * Intrinsics.ReciprocalSqrt(v);
|
|
||||||
var evalScores = exploitation + exploration;
|
|
||||||
|
|
||||||
var idx = Intrinsics.HMaxIndex(evalScores, iterCount);
|
|
||||||
|
|
||||||
if (evalScores[idx] >= maxScore)
|
|
||||||
{
|
|
||||||
max = (i, idx);
|
|
||||||
maxScore = evalScores[idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
length -= iterCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
return children.Data[max.Item1][max.Item2];
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static (Node ExpandedNode, float Score) Rollout(ref SolverConfig config, Node rootNode, Node expandedNode, Random random, Simulator simulator)
|
|
||||||
{
|
|
||||||
// playout to a terminal state
|
|
||||||
var currentState = expandedNode.State.State;
|
|
||||||
var currentCompletionState = expandedNode.State.SimulationCompletionState;
|
|
||||||
var currentActions = expandedNode.State.AvailableActions;
|
|
||||||
|
|
||||||
byte actionCount = 0;
|
|
||||||
Span<ActionType> actions = stackalloc ActionType[config.MaxStepCount - currentState.ActionCount];
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (SimulationNode.GetCompletionState(currentCompletionState, currentActions) != CompletionState.Incomplete)
|
|
||||||
break;
|
|
||||||
var nextAction = currentActions.SelectRandom(random);
|
|
||||||
actions[actionCount++] = nextAction;
|
|
||||||
(_, currentState) = simulator.Execute(currentState, nextAction);
|
|
||||||
currentCompletionState = simulator.CompletionState;
|
|
||||||
currentActions = simulator.AvailableActionsHeuristic(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// store the result if a max score was reached
|
|
||||||
var score = SimulationNode.CalculateScoreForState(currentState, currentCompletionState, config.MaxStepCount) ?? 0;
|
|
||||||
if (currentCompletionState == CompletionState.ProgressComplete)
|
|
||||||
{
|
|
||||||
if (score >= config.ScoreStorageThreshold && score >= rootNode.State.Scores.MaxScore)
|
|
||||||
{
|
|
||||||
(var terminalNode, _) = ExecuteActions(simulator, expandedNode, actions[..actionCount], true);
|
|
||||||
return (terminalNode, score);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (expandedNode, score);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static void Search<S>(ref SolverConfig config, int iterations, Node rootNode, CancellationToken token) where S : ISolver
|
|
||||||
{
|
|
||||||
Simulator simulator = new(rootNode.State.State, config.MaxStepCount);
|
|
||||||
var random = rootNode.State.State.Input.Random;
|
|
||||||
for (var i = 0; i < iterations; i++)
|
|
||||||
{
|
|
||||||
if (token.IsCancellationRequested)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (!S.SearchIter(ref config, rootNode, random, simulator))
|
|
||||||
{
|
|
||||||
// Retry, count this iteration as moot
|
|
||||||
i--;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Pure]
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static Node CreateRootNode(SolverConfig config, SimulationInput input, bool strict) =>
|
|
||||||
CreateRootNode(config, new SimulationState(input), strict);
|
|
||||||
|
|
||||||
[Pure]
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static Node CreateRootNode(SolverConfig config, SimulationState state, bool strict)
|
|
||||||
{
|
|
||||||
var sim = new Simulator(state, config.MaxStepCount);
|
|
||||||
return new(new(
|
|
||||||
state,
|
|
||||||
null,
|
|
||||||
sim.CompletionState,
|
|
||||||
sim.AvailableActionsHeuristic(strict)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static (List<ActionType> Actions, SimulationState State) SearchStepwise<S>(SolverConfig config, SimulationInput input, Action<ActionType>? actionCallback, CancellationToken token = default) where S : ISolver =>
|
|
||||||
SearchStepwise<S>(config, new SimulationState(input), actionCallback, token);
|
|
||||||
|
|
||||||
public static (List<ActionType> Actions, SimulationState State) SearchStepwise<S>(SolverConfig config, SimulationState state, Action<ActionType>? actionCallback, CancellationToken token = default) where S : ISolver
|
|
||||||
{
|
|
||||||
var actions = new List<ActionType>();
|
|
||||||
var sim = new Simulator(state, config.MaxStepCount);
|
|
||||||
var rootNode = CreateRootNode(config, state, true);
|
|
||||||
while (!sim.IsComplete)
|
|
||||||
{
|
|
||||||
if (token.IsCancellationRequested)
|
|
||||||
break;
|
|
||||||
|
|
||||||
S.Search(ref config, rootNode, token);
|
|
||||||
var (solution_actions, solution_node) = Solution(rootNode);
|
|
||||||
|
|
||||||
if (solution_node.Scores.MaxScore >= 1.0)
|
|
||||||
{
|
|
||||||
actions.AddRange(solution_actions);
|
|
||||||
return (actions, solution_node.State);
|
|
||||||
}
|
|
||||||
|
|
||||||
var chosen_action = solution_actions[0];
|
|
||||||
(_, state) = sim.Execute(state, chosen_action);
|
|
||||||
actions.Add(chosen_action);
|
|
||||||
|
|
||||||
actionCallback?.Invoke(chosen_action);
|
|
||||||
|
|
||||||
rootNode = CreateRootNode(config, state, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (actions, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static (List<ActionType> Actions, SimulationState State) SearchOneshot<S>(SolverConfig config, SimulationInput input, CancellationToken token = default) where S : ISolver =>
|
|
||||||
SearchOneshot<S>(config, new SimulationState(input), token);
|
|
||||||
|
|
||||||
public static (List<ActionType> Actions, SimulationState State) SearchOneshot<S>(SolverConfig config, SimulationState state, CancellationToken token = default) where S : ISolver
|
|
||||||
{
|
|
||||||
var rootNode = CreateRootNode(config, state, false);
|
|
||||||
S.Search(ref config, rootNode, token);
|
|
||||||
var (solution_actions, solution_node) = Solution(rootNode);
|
|
||||||
return (solution_actions, solution_node.State);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user