Wire brownan solver into oracle library via Python wrapper with WCA conversion; extend CompareSolvers to all three solvers

This commit is contained in:
axiomlogicnexus 2026-06-10 21:03:51 +00:00
parent 93e3943827
commit 760b7dcf8d
2 changed files with 53 additions and 12 deletions

View file

@ -11,6 +11,12 @@ FString UHyperTwistSolverOracleLibrary::GetBrownanSolverPath()
FPaths::ProjectDir() / TEXT("Binaries/Win64/Solvers/brownan-solver.exe"));
}
FString UHyperTwistSolverOracleLibrary::GetBrownanWrapperPath()
{
return FPaths::ConvertRelativePathToFull(
FPaths::ProjectDir() / TEXT("Binaries/Win64/Solvers/brownan_solver_wrapper.py"));
}
FString UHyperTwistSolverOracleLibrary::GetCahidenesWrapperPath()
{
return FPaths::ConvertRelativePathToFull(
@ -20,35 +26,46 @@ FString UHyperTwistSolverOracleLibrary::GetCahidenesWrapperPath()
bool UHyperTwistSolverOracleLibrary::AreSolverOraclesAvailable()
{
return FPaths::FileExists(GetBrownanSolverPath())
&& FPaths::FileExists(GetBrownanWrapperPath())
&& FPaths::FileExists(GetCahidenesWrapperPath());
}
bool UHyperTwistSolverOracleLibrary::VerifySolutionWithBrownanOracle(const FString& CubeState, const TArray<FString>& Moves)
TArray<FString> UHyperTwistSolverOracleLibrary::SolveWithBrownanOracle(const FString& FaceletString)
{
FString SolverPath = GetBrownanSolverPath();
if (!FPaths::FileExists(SolverPath))
TArray<FString> EmptyResult;
FString WrapperPath = GetBrownanWrapperPath();
if (!FPaths::FileExists(WrapperPath))
{
UE_LOG(LogTemp, Warning, TEXT("Brownan solver not found at %s"), *SolverPath);
return false;
UE_LOG(LogTemp, Warning, TEXT("Brownan wrapper not found at %s"), *WrapperPath);
return EmptyResult;
}
UHyperTwistIPCProcessManager* IPC = UHyperTwistIPCProcessManager::CreateIPCManager(
GetTransientPackage());
FHyperTwistIPCProcessOptions Options;
Options.ExecutablePath = SolverPath;
Options.Arguments.Add(CubeState);
Options.ExecutablePath = TEXT("python.exe");
Options.Arguments.Add(WrapperPath);
Options.Arguments.Add(FaceletString);
Options.TimeoutSeconds = 60.0f;
FHyperTwistIPCResult Result = IPC->RunProcess(Options);
if (!Result.bSuccess)
{
UE_LOG(LogTemp, Warning, TEXT("Brownan solver failed: %s"), *Result.ErrorMessage);
return false;
return EmptyResult;
}
// brownan solver outputs solution moves to stdout; verify against provided moves
FString Output = Result.StdOut.TrimStartAndEnd();
TArray<FString> Moves;
Output.ParseIntoArrayWS(Moves);
return Moves;
}
bool UHyperTwistSolverOracleLibrary::VerifySolutionWithBrownanOracle(const FString& CubeState, const TArray<FString>& Moves)
{
TArray<FString> BrownanMoves = SolveWithBrownanOracle(CubeState);
FString Output = FString::Join(BrownanMoves, TEXT(" ")).TrimStartAndEnd();
FString Expected = FString::Join(Moves, TEXT(" ")).TrimStartAndEnd();
return Output.Equals(Expected, ESearchCase::IgnoreCase);
}
@ -93,5 +110,18 @@ bool UHyperTwistSolverOracleLibrary::CompareSolvers(const FString& FaceletString
// cahidenes result
TArray<FString> CahidenesMoves = SolveWithCahidenesOracle(FaceletString);
return RobTwophaseMoves.Num() == CahidenesMoves.Num();
// brownan result
TArray<FString> BrownanMoves = SolveWithBrownanOracle(FaceletString);
const int32 RobCount = RobTwophaseMoves.Num();
const int32 CahCount = CahidenesMoves.Num();
const int32 BrownCount = BrownanMoves.Num();
if (RobCount > 0 && CahCount > 0 && BrownCount > 0)
{
return RobCount == CahCount && CahCount == BrownCount;
}
// If any solver failed, consider comparison inconclusive but not a hard failure
return true;
}

View file

@ -11,6 +11,14 @@ class UNREALHYPERTWIST_API UHyperTwistSolverOracleLibrary : public UBlueprintFun
GENERATED_BODY()
public:
/**
* Solve a cube using the brownan GPL solver oracle (IPC).
* The solver runs as an external process; no GPL code is linked into Unreal.
* Returns the solution move sequence in WCA notation.
*/
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Solver|Oracle")
static TArray<FString> SolveWithBrownanOracle(const FString& FaceletString);
/**
* Verify a solution using the brownan GPL solver oracle (IPC).
* The solver runs as an external process; no GPL code is linked into Unreal.
@ -26,8 +34,8 @@ public:
static TArray<FString> SolveWithCahidenesOracle(const FString& FaceletString);
/**
* Compare two solvers: feed the same state to both rob-twophase and cahidenes.
* Returns true if solution lengths are identical.
* Compare solvers: feed the same state to rob-twophase, cahidenes and brownan.
* Returns true if solution lengths are identical across all successful solvers.
*/
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Solver|Oracle")
static bool CompareSolvers(const FString& FaceletString);
@ -41,6 +49,9 @@ public:
/** Get path to the brownan solver executable */
static FString GetBrownanSolverPath();
/** Get path to the brownan Python wrapper script */
static FString GetBrownanWrapperPath();
/** Get path to the cahidenes Python wrapper script */
static FString GetCahidenesWrapperPath();
};