slime/show/08_seed_cpu.cpp

239 lines
8.9 KiB
C++

// Teaching snapshot extracted from research/slime_pattern_cpp.cpp.
// Shared Java arithmetic/profile solvers live in ../include/slime; see README.md for scope.
#include "slime_solver_core.hpp"
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <mutex>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
namespace sr = slime_research;
namespace {
struct CellOffset {
int32_t dx;
int32_t dz;
uint8_t requirement;
};
struct Args {
std::string command;
int32_t x = 0;
int32_t z = 0;
int32_t size = 5;
std::string pattern;
uint64_t seed = 0;
uint64_t lower35 = 0;
int bits = 24;
int threads = std::max(1U, std::thread::hardware_concurrency());
uint64_t maxResults = 1000;
};
[[noreturn]] void usage(int code = 1) {
std::cerr
<< "Usage:\n"
<< " slime_pattern_cpp verify --seed N --x X --z Z [-n N|--pattern ROW/ROW]\n"
<< " slime_pattern_cpp upper --lower35 N --x X --z Z [-n N|--pattern ...]\n"
<< " slime_pattern_cpp roots --x X --z Z [--bits 18..35] [-n N|--pattern ...]\n"
<< " slime_pattern_cpp search --x X --z Z [-p THREADS] [--max-results N] ...\n";
std::exit(code);
}
const char* value(int argc, char** argv, int& i) {
if (++i >= argc) usage();
return argv[i];
}
Args parseArgs(int argc, char** argv) {
if (argc < 2) usage();
Args args;
args.command = argv[1];
for (int i = 2; i < argc; ++i) {
const std::string arg = argv[i];
if (arg == "--x") args.x = std::stoi(value(argc, argv, i));
else if (arg == "--z") args.z = std::stoi(value(argc, argv, i));
else if (arg == "-n" || arg == "--size") args.size = std::stoi(value(argc, argv, i));
else if (arg == "--pattern") args.pattern = value(argc, argv, i);
else if (arg == "--seed") args.seed = std::stoull(value(argc, argv, i));
else if (arg == "--lower35") args.lower35 = std::stoull(value(argc, argv, i));
else if (arg == "--bits") args.bits = std::stoi(value(argc, argv, i));
else if (arg == "-p" || arg == "--threads") args.threads = std::stoi(value(argc, argv, i));
else if (arg == "--max-results") args.maxResults = std::stoull(value(argc, argv, i));
else if (arg == "--help") usage(0);
else usage();
}
if (args.size <= 0 || args.bits < 18 || args.bits > 35 || args.threads <= 0) usage();
return args;
}
std::vector<CellOffset> parsePattern(const Args& args) {
std::vector<CellOffset> offsets;
if (args.pattern.empty()) {
if (int64_t(args.size) * args.size > sr::kMaxPatternCells) {
throw std::runtime_error("pattern exceeds the 64-cell prototype limit");
}
for (int32_t dz = 0; dz < args.size; ++dz) {
for (int32_t dx = 0; dx < args.size; ++dx) {
offsets.push_back({dx, dz, sr::kSlime});
}
}
return offsets;
}
int32_t dx = 0;
int32_t dz = 0;
int32_t width = -1;
for (size_t i = 0; i <= args.pattern.size(); ++i) {
const char c = i == args.pattern.size() ? '/' : args.pattern[i];
if (c == '/') {
if (dx == 0 || (width >= 0 && dx != width)) {
throw std::runtime_error("pattern rows must have equal non-zero width");
}
width = dx;
dx = 0;
++dz;
continue;
}
if (c == '#') offsets.push_back({dx, dz, sr::kSlime});
else if (c == '.') offsets.push_back({dx, dz, sr::kNonSlime});
else if (c != '?') throw std::runtime_error("pattern accepts only #, ., ?, and /");
++dx;
}
if (offsets.empty() || offsets.size() > sr::kMaxPatternCells) {
throw std::runtime_error("pattern must contain 1..64 constrained cells");
}
return offsets;
}
std::vector<sr::CellTerm> makeTerms(const Args& args) {
std::vector<sr::CellTerm> terms;
for (const CellOffset& cell : parsePattern(args)) {
terms.push_back({
sr::coordinateTerm(args.x + cell.dx, args.z + cell.dz),
cell.requirement,
});
}
return terms;
}
std::vector<uint32_t> roots18(const std::vector<sr::CellTerm>& terms) {
std::vector<uint32_t> roots;
for (uint32_t seed = 0; seed < (1U << 18); ++seed) {
if (sr::partialPatternPossible(seed, 18, terms.data(), int(terms.size()))) {
roots.push_back(seed);
}
}
return roots;
}
struct VectorEmitter {
std::vector<uint64_t>& values;
void operator()(uint64_t seed) { values.push_back(seed); }
};
int commandVerify(const Args& args, const std::vector<sr::CellTerm>& terms) {
const bool match = sr::verifyPattern(args.seed & sr::kMask48, terms.data(), int(terms.size()));
std::cout << "seed48=" << (args.seed & sr::kMask48) << ' '
<< (match ? "match" : "no-match") << '\n';
return match ? 0 : 1;
}
int commandUpper(const Args& args, const std::vector<sr::CellTerm>& terms) {
std::vector<uint64_t> results;
VectorEmitter emit{results};
sr::solveUpper13(args.lower35, terms.data(), int(terms.size()), emit);
std::sort(results.begin(), results.end());
std::cout << "lower35=" << (args.lower35 & ((1ULL << 35) - 1ULL))
<< " exact_matches=" << results.size() << '\n';
for (uint64_t seed : results) std::cout << seed << '\n';
return 0;
}
int commandRoots(const Args& args, const std::vector<sr::CellTerm>& terms) {
std::vector<uint32_t> roots = roots18(terms);
std::cout << "bits=18 candidates=" << roots.size() << '\n';
for (int bits = 19; bits <= args.bits; ++bits) {
const uint32_t high = 1U << (bits - 1);
const size_t oldSize = roots.size();
roots.reserve(oldSize * 2);
for (size_t i = 0; i < oldSize; ++i) roots.push_back(roots[i] | high);
roots.erase(std::remove_if(roots.begin(), roots.end(), [&](uint32_t seed) {
return !sr::partialPatternPossible(seed, bits, terms.data(), int(terms.size()));
}), roots.end());
std::cout << "bits=" << bits << " candidates=" << roots.size() << '\n';
}
return 0;
}
int commandSearch(const Args& args, const std::vector<sr::CellTerm>& terms) {
const std::vector<uint32_t> roots = roots18(terms);
constexpr uint64_t extensions = 1ULL << (35 - 18);
const uint64_t workItems = uint64_t(roots.size()) * extensions;
std::atomic<uint64_t> next{0};
std::atomic<uint64_t> totalResults{0};
std::mutex resultMutex;
std::vector<uint64_t> results;
const auto start = std::chrono::steady_clock::now();
auto worker = [&] {
std::vector<uint64_t> local;
local.reserve(64);
VectorEmitter emit{local};
constexpr uint64_t batch = 256;
for (;;) {
const uint64_t begin = next.fetch_add(batch, std::memory_order_relaxed);
if (begin >= workItems) break;
const uint64_t end = std::min(begin + batch, workItems);
for (uint64_t index = begin; index < end; ++index) {
const uint64_t rootIndex = index >> (35 - 18);
const uint64_t extension = index & (extensions - 1);
const uint64_t lower35 = roots[rootIndex] | (extension << 18);
sr::solveUpper13(lower35, terms.data(), int(terms.size()), emit);
}
if (!local.empty()) {
totalResults.fetch_add(local.size(), std::memory_order_relaxed);
std::lock_guard<std::mutex> lock(resultMutex);
const size_t room = results.size() < args.maxResults
? size_t(args.maxResults - results.size()) : 0;
const size_t take = std::min(room, local.size());
results.insert(results.end(), local.begin(), local.begin() + take);
local.clear();
}
}
};
std::vector<std::thread> threads;
for (int i = 0; i < args.threads; ++i) threads.emplace_back(worker);
for (std::thread& thread : threads) thread.join();
std::sort(results.begin(), results.end());
const std::chrono::duration<double> elapsed = std::chrono::steady_clock::now() - start;
std::cout << "lower18_roots=" << roots.size() << " lower35_checked=" << workItems
<< " exact_matches=" << totalResults.load() << " seconds=" << elapsed.count() << '\n';
for (uint64_t seed : results) std::cout << seed << '\n';
return 0;
}
} // namespace
int main(int argc, char** argv) {
try {
const Args args = parseArgs(argc, argv);
const std::vector<sr::CellTerm> terms = makeTerms(args);
if (args.command == "verify") return commandVerify(args, terms);
if (args.command == "upper") return commandUpper(args, terms);
if (args.command == "roots") return commandRoots(args, terms);
if (args.command == "search") return commandSearch(args, terms);
usage();
} catch (const std::exception& error) {
std::cerr << "error: " << error.what() << '\n';
return 2;
}
}