Solver: Add Raphael

This commit is contained in:
Asriel Camora
2024-11-28 02:37:54 -08:00
parent 12d46bdd26
commit 9a39ef936e
5 changed files with 229 additions and 0 deletions
+1
View File
@@ -15,6 +15,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Raphael.Net" Version="1.0.0" />
</ItemGroup>
<ItemGroup>
+104
View File
@@ -0,0 +1,104 @@
using Craftimizer.Simulator.Actions;
using Action = Raphael.Action;
namespace Craftimizer.Solver;
internal static unsafe class RaphaelUtils
{
public static ActionType[] ConvertRawActions(IReadOnlyList<Action> actions)
{
var result = new ActionType[actions.Count];
for (var i = 0; i < actions.Count; i++)
result[i] = ConvertRawAction(actions[i]);
return result;
}
public static Action[] ConvertToRawActions(IReadOnlyList<ActionType> actions)
{
var result = new List<Action>(actions.Count);
foreach(var action in actions)
{
if (ConvertToRawAction(action) is { } a)
result.Add(a);
}
return [.. result];
}
public static ActionType ConvertRawAction(Action action)
{
return action switch
{
Action.BasicSynthesis => ActionType.BasicSynthesis,
Action.BasicTouch => ActionType.BasicTouch,
Action.MasterMend => ActionType.MastersMend,
Action.Observe => ActionType.Observe,
Action.TricksOfTheTrade => ActionType.TricksOfTheTrade,
Action.WasteNot => ActionType.WasteNot,
Action.Veneration => ActionType.Veneration,
Action.StandardTouch => ActionType.StandardTouch,
Action.GreatStrides => ActionType.GreatStrides,
Action.Innovation => ActionType.Innovation,
Action.WasteNot2 => ActionType.WasteNot2,
Action.ByregotsBlessing => ActionType.ByregotsBlessing,
Action.PreciseTouch => ActionType.PreciseTouch,
Action.MuscleMemory => ActionType.MuscleMemory,
Action.CarefulSynthesis => ActionType.CarefulSynthesis,
Action.Manipulation => ActionType.Manipulation,
Action.PrudentTouch => ActionType.PrudentTouch,
Action.AdvancedTouch => ActionType.AdvancedTouch,
Action.Reflect => ActionType.Reflect,
Action.PreparatoryTouch => ActionType.PreparatoryTouch,
Action.Groundwork => ActionType.Groundwork,
Action.DelicateSynthesis => ActionType.DelicateSynthesis,
Action.IntensiveSynthesis => ActionType.IntensiveSynthesis,
Action.TrainedEye => ActionType.TrainedEye,
Action.HeartAndSoul => ActionType.HeartAndSoul,
Action.PrudentSynthesis => ActionType.PrudentSynthesis,
Action.TrainedFinesse => ActionType.TrainedFinesse,
Action.RefinedTouch => ActionType.RefinedTouch,
Action.QuickInnovation => ActionType.QuickInnovation,
Action.ImmaculateMend => ActionType.ImmaculateMend,
Action.TrainedPerfection => ActionType.TrainedPerfection,
_ => throw new ArgumentOutOfRangeException(nameof(action), action, $"Invalid action value {action}"),
};
}
public static Action? ConvertToRawAction(ActionType action)
{
return action switch
{
ActionType.BasicSynthesis => Action.BasicSynthesis,
ActionType.BasicTouch => Action.BasicTouch,
ActionType.MastersMend => Action.MasterMend,
ActionType.Observe => Action.Observe,
ActionType.TricksOfTheTrade => Action.TricksOfTheTrade,
ActionType.WasteNot => Action.WasteNot,
ActionType.Veneration => Action.Veneration,
ActionType.StandardTouch => Action.StandardTouch,
ActionType.GreatStrides => Action.GreatStrides,
ActionType.Innovation => Action.Innovation,
ActionType.WasteNot2 => Action.WasteNot2,
ActionType.ByregotsBlessing => Action.ByregotsBlessing,
ActionType.PreciseTouch => Action.PreciseTouch,
ActionType.MuscleMemory => Action.MuscleMemory,
ActionType.CarefulSynthesis => Action.CarefulSynthesis,
ActionType.Manipulation => Action.Manipulation,
ActionType.PrudentTouch => Action.PrudentTouch,
ActionType.AdvancedTouch => Action.AdvancedTouch,
ActionType.Reflect => Action.Reflect,
ActionType.PreparatoryTouch => Action.PreparatoryTouch,
ActionType.Groundwork => Action.Groundwork,
ActionType.DelicateSynthesis => Action.DelicateSynthesis,
ActionType.IntensiveSynthesis => Action.IntensiveSynthesis,
ActionType.TrainedEye => Action.TrainedEye,
ActionType.HeartAndSoul => Action.HeartAndSoul,
ActionType.PrudentSynthesis => Action.PrudentSynthesis,
ActionType.TrainedFinesse => Action.TrainedFinesse,
ActionType.RefinedTouch => Action.RefinedTouch,
ActionType.QuickInnovation => Action.QuickInnovation,
ActionType.ImmaculateMend => Action.ImmaculateMend,
ActionType.TrainedPerfection => Action.TrainedPerfection,
_ => null,
};
}
}
+86
View File
@@ -39,6 +39,8 @@ public sealed class Solver : IDisposable
}
}
public bool IsIndeterminate => progress == 0 && progressStage == 0;
public delegate void LogDelegate(string text);
public delegate void NewActionDelegate(ActionType action);
public delegate void SolutionDelegate(SolverSolution solution);
@@ -46,9 +48,16 @@ public sealed class Solver : IDisposable
// Print to console or plugin log.
public event LogDelegate? OnLog;
// Display as notification.
public event LogDelegate? OnWarn;
// Always called when a new step is generated.
public event NewActionDelegate? OnNewAction;
// Called when the solver can provide a "probable" solution.
// OnNewAction actions precede these proposed solutions. Purely visual.
public event SolutionDelegate? OnSuggestSolution;
public Solver(in SolverConfig config, in SimulationState state)
{
Config = config;
@@ -61,6 +70,7 @@ public sealed class Solver : IDisposable
SolverAlgorithm.Stepwise => (SearchStepwise, true),
SolverAlgorithm.StepwiseForked => (SearchStepwiseForked, true),
SolverAlgorithm.StepwiseGenetic => (SearchStepwiseGenetic, true),
SolverAlgorithm.Raphael => (SearchRaphael, true),
_ => throw new ArgumentOutOfRangeException(nameof(config), config, $"Invalid algorithm: {config.Algorithm}")
});
@@ -136,6 +146,82 @@ public sealed class Solver : IDisposable
Interlocked.Increment(ref progressStage);
}
private async Task<SolverSolution> SearchRaphael()
{
if (State.ActionCount > 0)
{
OnWarn?.Invoke("Optimal solver not support existing actions; falling back to Stepwise Genetic.");
return await SearchStepwiseGenetic().ConfigureAwait(false);
}
maxProgress = 2000;
Raphael.SolverConfig config = new()
{
Adversarial = Config.Adversarial,
BackloadProgress = Config.BackloadProgress,
UnsoundBranchPruning = Config.UnsoundBranchPruning,
};
ActionType[]? solution = null;
var s = new SimulatorNoRandom() { State = State };
var pool = RaphaelUtils.ConvertToRawActions(Config.ActionPool.Where(a => a.Base().IsPossible(s)).ToArray());
var input = new Raphael.SolverInput()
{
CP = checked((short)State.Input.Stats.CP),
Durability = checked((sbyte)State.Input.Recipe.MaxDurability),
Progress = checked((ushort)State.Input.Recipe.MaxProgress),
Quality = checked((ushort)(State.Input.Recipe.MaxQuality - State.Input.StartingQuality)),
BaseProgressGain = checked((ushort)State.Input.BaseProgressGain),
BaseQualityGain = checked((ushort)State.Input.BaseQualityGain),
JobLevel = checked((byte)State.Input.Stats.Level),
};
using Raphael.Solver solver = new(in config, input, pool);
solver.OnFinish += s => solution = RaphaelUtils.ConvertRawActions(s);
solver.OnSuggestSolution += s =>
{
var steps = RaphaelUtils.ConvertRawActions(s);
var sim = new SimulatorNoRandom();
var (resp, outState, failedIdx) = sim.ExecuteMultiple(State, steps);
if (resp != ActionResponse.SimulationComplete)
{
if (failedIdx != -1)
OnLog?.Invoke($"Invalid state; simulation failed to execute solution: {string.Join(',', s)}");
}
OnSuggestSolution?.Invoke(new(steps, in outState));
};
solver.OnProgress += p =>
{
var prog = checked((int)p);
var stage = prog / maxProgress;
while (stage != progressStage)
ResetProgress();
progress = prog % maxProgress;
};
await using var registration = Token.Register(solver.Cancel).ConfigureAwait(true);
await Task.Run(solver.Solve, Token).ConfigureAwait(true);
if (solution == null)
return new([], State);
foreach (var action in solution)
InvokeNewAction(action);
var sim = new SimulatorNoRandom();
var (resp, outState, failedIdx) = sim.ExecuteMultiple(State, solution);
if (resp != ActionResponse.SimulationComplete)
{
if (failedIdx != -1)
throw new Exception($"Invalid state; simulation failed to execute solution: {string.Join(',', solution)}");
}
return new(solution, outState);
}
private async Task<SolverSolution> SearchStepwiseGenetic()
{
var iterCount = Config.Iterations / Config.ForkCount;
+5
View File
@@ -11,6 +11,7 @@ public enum SolverAlgorithm
Stepwise,
StepwiseForked,
StepwiseGenetic,
Raphael,
}
[StructLayout(LayoutKind.Auto)]
@@ -33,6 +34,10 @@ public readonly record struct SolverConfig
public float ScoreCP { get; init; }
public float ScoreSteps { get; init; }
public bool Adversarial { get; init; }
public bool BackloadProgress { get; init; }
public bool UnsoundBranchPruning { get; init; }
public ActionType[] ActionPool { get; init; }
public SolverAlgorithm Algorithm { get; init; }