From 9a39ef936e930f80ee0598ca01653354cf3b698e Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Thu, 28 Nov 2024 02:37:54 -0800 Subject: [PATCH] Solver: Add Raphael --- Craftimizer.sln | 33 ++++++++++ Solver/Craftimizer.Solver.csproj | 1 + Solver/RaphaelUtils.cs | 104 +++++++++++++++++++++++++++++++ Solver/Solver.cs | 86 +++++++++++++++++++++++++ Solver/SolverConfig.cs | 5 ++ 5 files changed, 229 insertions(+) create mode 100644 Solver/RaphaelUtils.cs diff --git a/Craftimizer.sln b/Craftimizer.sln index b20584e..891d739 100644 --- a/Craftimizer.sln +++ b/Craftimizer.sln @@ -21,37 +21,70 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Craftimizer.Test", "Test\Cr EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 + Deterministic|Any CPU = Deterministic|Any CPU Deterministic|x64 = Deterministic|x64 + Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|Any CPU.ActiveCfg = Debug|x64 + {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|Any CPU.Build.0 = Debug|x64 {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.ActiveCfg = Debug|x64 {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.Build.0 = Debug|x64 + {13C812E9-0D42-4B95-8646-40EEBF30636F}.Deterministic|Any CPU.ActiveCfg = Debug|x64 + {13C812E9-0D42-4B95-8646-40EEBF30636F}.Deterministic|Any CPU.Build.0 = Debug|x64 {13C812E9-0D42-4B95-8646-40EEBF30636F}.Deterministic|x64.ActiveCfg = Release|x64 + {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|Any CPU.ActiveCfg = Release|x64 + {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|Any CPU.Build.0 = Release|x64 {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.ActiveCfg = Release|x64 {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.Build.0 = Release|x64 + {057C4B64-4D99-4847-9BCF-966571CAE57C}.Debug|Any CPU.ActiveCfg = Debug|x64 + {057C4B64-4D99-4847-9BCF-966571CAE57C}.Debug|Any CPU.Build.0 = Debug|x64 {057C4B64-4D99-4847-9BCF-966571CAE57C}.Debug|x64.ActiveCfg = Debug|x64 {057C4B64-4D99-4847-9BCF-966571CAE57C}.Debug|x64.Build.0 = Debug|x64 + {057C4B64-4D99-4847-9BCF-966571CAE57C}.Deterministic|Any CPU.ActiveCfg = Deterministic|x64 + {057C4B64-4D99-4847-9BCF-966571CAE57C}.Deterministic|Any CPU.Build.0 = Deterministic|x64 {057C4B64-4D99-4847-9BCF-966571CAE57C}.Deterministic|x64.ActiveCfg = Deterministic|x64 {057C4B64-4D99-4847-9BCF-966571CAE57C}.Deterministic|x64.Build.0 = Deterministic|x64 + {057C4B64-4D99-4847-9BCF-966571CAE57C}.Release|Any CPU.ActiveCfg = Release|x64 + {057C4B64-4D99-4847-9BCF-966571CAE57C}.Release|Any CPU.Build.0 = Release|x64 {057C4B64-4D99-4847-9BCF-966571CAE57C}.Release|x64.ActiveCfg = Release|x64 {057C4B64-4D99-4847-9BCF-966571CAE57C}.Release|x64.Build.0 = Release|x64 + {172EE849-AC7E-4F2A-ACAB-EF9D065523B3}.Debug|Any CPU.ActiveCfg = Debug|x64 + {172EE849-AC7E-4F2A-ACAB-EF9D065523B3}.Debug|Any CPU.Build.0 = Debug|x64 {172EE849-AC7E-4F2A-ACAB-EF9D065523B3}.Debug|x64.ActiveCfg = Debug|x64 {172EE849-AC7E-4F2A-ACAB-EF9D065523B3}.Debug|x64.Build.0 = Debug|x64 + {172EE849-AC7E-4F2A-ACAB-EF9D065523B3}.Deterministic|Any CPU.ActiveCfg = Deterministic|x64 + {172EE849-AC7E-4F2A-ACAB-EF9D065523B3}.Deterministic|Any CPU.Build.0 = Deterministic|x64 {172EE849-AC7E-4F2A-ACAB-EF9D065523B3}.Deterministic|x64.ActiveCfg = Deterministic|x64 {172EE849-AC7E-4F2A-ACAB-EF9D065523B3}.Deterministic|x64.Build.0 = Deterministic|x64 + {172EE849-AC7E-4F2A-ACAB-EF9D065523B3}.Release|Any CPU.ActiveCfg = Release|x64 + {172EE849-AC7E-4F2A-ACAB-EF9D065523B3}.Release|Any CPU.Build.0 = Release|x64 {172EE849-AC7E-4F2A-ACAB-EF9D065523B3}.Release|x64.ActiveCfg = Release|x64 {172EE849-AC7E-4F2A-ACAB-EF9D065523B3}.Release|x64.Build.0 = Release|x64 + {2B0EA452-6DFC-48DB-9049-EA782E600C21}.Debug|Any CPU.ActiveCfg = Debug|x64 + {2B0EA452-6DFC-48DB-9049-EA782E600C21}.Debug|Any CPU.Build.0 = Debug|x64 {2B0EA452-6DFC-48DB-9049-EA782E600C21}.Debug|x64.ActiveCfg = Debug|x64 {2B0EA452-6DFC-48DB-9049-EA782E600C21}.Debug|x64.Build.0 = Debug|x64 + {2B0EA452-6DFC-48DB-9049-EA782E600C21}.Deterministic|Any CPU.ActiveCfg = Deterministic|x64 + {2B0EA452-6DFC-48DB-9049-EA782E600C21}.Deterministic|Any CPU.Build.0 = Deterministic|x64 {2B0EA452-6DFC-48DB-9049-EA782E600C21}.Deterministic|x64.ActiveCfg = Deterministic|x64 {2B0EA452-6DFC-48DB-9049-EA782E600C21}.Deterministic|x64.Build.0 = Deterministic|x64 + {2B0EA452-6DFC-48DB-9049-EA782E600C21}.Release|Any CPU.ActiveCfg = Release|x64 + {2B0EA452-6DFC-48DB-9049-EA782E600C21}.Release|Any CPU.Build.0 = Release|x64 {2B0EA452-6DFC-48DB-9049-EA782E600C21}.Release|x64.ActiveCfg = Release|x64 {2B0EA452-6DFC-48DB-9049-EA782E600C21}.Release|x64.Build.0 = Release|x64 + {C3AEA981-9DA8-405C-995B-86528493891B}.Debug|Any CPU.ActiveCfg = Debug|x64 + {C3AEA981-9DA8-405C-995B-86528493891B}.Debug|Any CPU.Build.0 = Debug|x64 {C3AEA981-9DA8-405C-995B-86528493891B}.Debug|x64.ActiveCfg = Debug|x64 {C3AEA981-9DA8-405C-995B-86528493891B}.Debug|x64.Build.0 = Debug|x64 + {C3AEA981-9DA8-405C-995B-86528493891B}.Deterministic|Any CPU.ActiveCfg = Deterministic|x64 + {C3AEA981-9DA8-405C-995B-86528493891B}.Deterministic|Any CPU.Build.0 = Deterministic|x64 {C3AEA981-9DA8-405C-995B-86528493891B}.Deterministic|x64.ActiveCfg = Deterministic|x64 + {C3AEA981-9DA8-405C-995B-86528493891B}.Release|Any CPU.ActiveCfg = Release|x64 + {C3AEA981-9DA8-405C-995B-86528493891B}.Release|Any CPU.Build.0 = Release|x64 {C3AEA981-9DA8-405C-995B-86528493891B}.Release|x64.ActiveCfg = Release|x64 {C3AEA981-9DA8-405C-995B-86528493891B}.Release|x64.Build.0 = Release|x64 EndGlobalSection diff --git a/Solver/Craftimizer.Solver.csproj b/Solver/Craftimizer.Solver.csproj index 8a52408..a79793d 100644 --- a/Solver/Craftimizer.Solver.csproj +++ b/Solver/Craftimizer.Solver.csproj @@ -15,6 +15,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Solver/RaphaelUtils.cs b/Solver/RaphaelUtils.cs new file mode 100644 index 0000000..52dccb2 --- /dev/null +++ b/Solver/RaphaelUtils.cs @@ -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 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 actions) + { + var result = new List(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, + }; + } +} diff --git a/Solver/Solver.cs b/Solver/Solver.cs index a848bf3..b06c68a 100644 --- a/Solver/Solver.cs +++ b/Solver/Solver.cs @@ -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 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 SearchStepwiseGenetic() { var iterCount = Config.Iterations / Config.ForkCount; diff --git a/Solver/SolverConfig.cs b/Solver/SolverConfig.cs index f125ca8..82a4d1e 100644 --- a/Solver/SolverConfig.cs +++ b/Solver/SolverConfig.cs @@ -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; }