├── Benchmark.cpp ├── CMakeLists.txt ├── Fission.cpp ├── Fission.h ├── FissionNet.cpp ├── FissionNet.h ├── OptFission.cpp ├── OptFission.h ├── OptOverhaulFission.cpp ├── OptOverhaulFission.h ├── OverhaulFission.cpp ├── OverhaulFission.h ├── OverhaulFissionNet.cpp ├── OverhaulFissionNet.h ├── README.md └── web ├── Bindings.cpp ├── compile.bat ├── index.html ├── main.css ├── main.js ├── overhaul.css ├── overhaul.html └── overhaul.js /Benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "OverhaulFissionNet.h" 3 | 4 | int main() { 5 | OverhaulFission::Settings settings { 6 | 7, 7, 7, 7 | { 8 | {1.75, -1, 60, 540, true}, 9 | {1.75, -1, 75, 432, true}, 10 | {1.75, -1, 51, 676, true}, 11 | {1.80, -1, 30, 1620, true}, 12 | {1.80, -1, 37, 1296, true}, 13 | {1.80, -1, 25, 2028, true}, 14 | {1.80, -1, 71, 288, true}, 15 | {1.80, -1, 89, 230, true}, 16 | {1.80, -1, 60, 360, true}, 17 | {1.85, -1, 35, 864, true}, 18 | {1.85, -1, 44, 690, true}, 19 | {1.85, -1, 30, 1080, true} 20 | }, 21 | { 22 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 23 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 24 | -1, -1, -1, -1, -1, -1, -1, -1 25 | }, 26 | {0, 0, 0}, 27 | OverhaulFission::GoalOutput, 28 | false, false, true, true 29 | }; 30 | OverhaulFission::Opt opt(settings); 31 | while (true) { 32 | opt.stepInteractive(); 33 | if (opt.needsRedrawBest()) { 34 | std::cout << "output: " << opt.getBest().value.output / 16 << std::endl; 35 | xt::dump_npy("best.npy", opt.getBest().state); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(FissionOpt) 3 | set(CMAKE_CXX_STANDARD 17) 4 | 5 | add_executable(FissionOpt 6 | Fission.h 7 | Fission.cpp 8 | OptFission.h 9 | OptFission.cpp 10 | FissionNet.h 11 | FissionNet.cpp 12 | Benchmark.cpp 13 | OverhaulFission.h 14 | OverhaulFission.cpp 15 | OptOverhaulFission.h 16 | OptOverhaulFission.cpp 17 | OverhaulFissionNet.h 18 | OverhaulFissionNet.cpp 19 | ) 20 | 21 | target_include_directories(FissionOpt PRIVATE 22 | ../xtensor/include 23 | ../xtl/include 24 | ) 25 | -------------------------------------------------------------------------------- /Fission.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Fission.h" 3 | 4 | namespace Fission { 5 | void Evaluation::compute(const Settings &settings) { 6 | heat = settings.fuelBaseHeat * heatMult; 7 | netHeat = heat - cooling; 8 | dutyCycle = std::min(1.0, cooling / heat); 9 | avgMult = powerMult * dutyCycle; 10 | power = powerMult * settings.fuelBasePower; 11 | avgPower = power * dutyCycle; 12 | avgBreed = breed * dutyCycle; 13 | efficiency = breed ? powerMult / breed : 1.0; 14 | } 15 | 16 | Evaluator::Evaluator(const Settings &settings) 17 | :settings(settings), 18 | mults(xt::empty({settings.sizeX, settings.sizeY, settings.sizeZ})), 19 | rules(xt::empty({settings.sizeX, settings.sizeY, settings.sizeZ})), 20 | isActive(xt::empty({settings.sizeX, settings.sizeY, settings.sizeZ})), 21 | isModeratorInLine(xt::empty({settings.sizeX, settings.sizeY, settings.sizeZ})), 22 | visited(xt::empty({settings.sizeX, settings.sizeY, settings.sizeZ})) {} 23 | 24 | int Evaluator::getTileSafe(int x, int y, int z) const { 25 | if (!state->in_bounds(x, y, z)) 26 | return -1; 27 | return (*state)(x, y, z); 28 | } 29 | 30 | int Evaluator::getMultSafe(int x, int y, int z) const { 31 | if (!mults.in_bounds(x, y, z)) 32 | return 0; 33 | return mults(x, y, z); 34 | } 35 | 36 | bool Evaluator::countMult(int x, int y, int z, int dx, int dy, int dz) { 37 | for (int n{}; n <= neutronReach; ++n) { 38 | x += dx; y += dy; z += dz; 39 | int tile(getTileSafe(x, y, z)); 40 | if (tile == Cell) { 41 | for (int i{}; i < n; ++i) { 42 | x -= dx; y -= dy; z -= dz; 43 | isModeratorInLine(x, y, z) = true; 44 | } 45 | return true; 46 | } else if (tile != Moderator) { 47 | return false; 48 | } 49 | } 50 | return false; 51 | } 52 | 53 | int Evaluator::countMult(int x, int y, int z) { 54 | return 1 55 | + countMult(x, y, z, -1, 0, 0) 56 | + countMult(x, y, z, +1, 0, 0) 57 | + countMult(x, y, z, 0, -1, 0) 58 | + countMult(x, y, z, 0, +1, 0) 59 | + countMult(x, y, z, 0, 0, -1) 60 | + countMult(x, y, z, 0, 0, +1); 61 | } 62 | 63 | bool Evaluator::isActiveSafe(int tile, int x, int y, int z) const { 64 | if (!state->in_bounds(x, y, z)) 65 | return false; 66 | return (*state)(x, y, z) == tile && isActive(x, y, z); 67 | } 68 | 69 | int Evaluator::countActiveNeighbors(int tile, int x, int y, int z) const { 70 | return 71 | + isActiveSafe(tile, x - 1, y, z) 72 | + isActiveSafe(tile, x + 1, y, z) 73 | + isActiveSafe(tile, x, y - 1, z) 74 | + isActiveSafe(tile, x, y + 1, z) 75 | + isActiveSafe(tile, x, y, z - 1) 76 | + isActiveSafe(tile, x, y, z + 1); 77 | } 78 | 79 | bool Evaluator::isTileSafe(int tile, int x, int y, int z) const { 80 | if (!state->in_bounds(x, y, z)) 81 | return false; 82 | return (*state)(x, y, z) == tile; 83 | } 84 | 85 | int Evaluator::countNeighbors(int tile, int x, int y, int z) const { 86 | return 87 | + isTileSafe(tile, x - 1, y, z) 88 | + isTileSafe(tile, x + 1, y, z) 89 | + isTileSafe(tile, x, y - 1, z) 90 | + isTileSafe(tile, x, y + 1, z) 91 | + isTileSafe(tile, x, y, z - 1) 92 | + isTileSafe(tile, x, y, z + 1); 93 | } 94 | 95 | int Evaluator::countCasingNeighbors(int x, int y, int z) const { 96 | return 97 | + !state->in_bounds(x - 1, y, z) 98 | + !state->in_bounds(x + 1, y, z) 99 | + !state->in_bounds(x, y - 1, z) 100 | + !state->in_bounds(x, y + 1, z) 101 | + !state->in_bounds(x, y, z - 1) 102 | + !state->in_bounds(x, y, z + 1); 103 | } 104 | 105 | bool Evaluator::checkAccessibility(int compatibleTile, int x, int y, int z) { 106 | visited.fill(false); 107 | this->compatibleTile = compatibleTile; 108 | return checkAccessibility(x, y, z); 109 | } 110 | 111 | bool Evaluator::checkAccessibility(int x, int y, int z) { 112 | if (!state->in_bounds(x, y, z)) 113 | return true; 114 | if (visited(x, y, z)) 115 | return false; 116 | visited(x, y, z) = true; 117 | int tile((*state)(x, y, z)); 118 | if (tile != Air && tile != compatibleTile) 119 | return false; 120 | return 121 | checkAccessibility(x - 1, y, z) || 122 | checkAccessibility(x + 1, y, z) || 123 | checkAccessibility(x, y - 1, z) || 124 | checkAccessibility(x, y + 1, z) || 125 | checkAccessibility(x, y, z - 1) || 126 | checkAccessibility(x, y, z + 1); 127 | } 128 | 129 | void Evaluator::run(const xt::xtensor &state, Evaluation &result) { 130 | result.invalidTiles.clear(); 131 | result.powerMult = 0.0; 132 | result.heatMult = 0.0; 133 | result.cooling = 0.0; 134 | result.breed = 0; 135 | isActive.fill(false); 136 | isModeratorInLine.fill(false); 137 | this->state = &state; 138 | for (int x{}; x < settings.sizeX; ++x) { 139 | for (int y{}; y < settings.sizeY; ++y) { 140 | for (int z{}; z < settings.sizeZ; ++z) { 141 | int tile((*this->state)(x, y, z)); 142 | if (tile == Cell) { 143 | int mult(countMult(x, y, z)); 144 | mults(x, y, z) = mult; 145 | rules(x, y, z) = -1; 146 | ++result.breed; 147 | result.powerMult += mult; 148 | result.heatMult += mult * (mult + 1) / 2.0; 149 | } else { 150 | mults(x, y, z) = 0; 151 | if (tile < Active) { 152 | rules(x, y, z) = tile; 153 | } else if (tile < Cell) { 154 | if (settings.ensureActiveCoolerAccessible && !checkAccessibility(tile, x, y, z)) { 155 | rules(x, y, z) = -1; 156 | } else { 157 | rules(x, y, z) = tile - Active; 158 | } 159 | } else { 160 | rules(x, y, z) = -1; 161 | } 162 | } 163 | } 164 | } 165 | } 166 | 167 | for (int x{}; x < settings.sizeX; ++x) { 168 | for (int y{}; y < settings.sizeY; ++y) { 169 | for (int z{}; z < settings.sizeZ; ++z) { 170 | if ((*this->state)(x, y, z) == Moderator) { 171 | int mult( 172 | + getMultSafe(x - 1, y, z) 173 | + getMultSafe(x + 1, y, z) 174 | + getMultSafe(x, y - 1, z) 175 | + getMultSafe(x, y + 1, z) 176 | + getMultSafe(x, y, z - 1) 177 | + getMultSafe(x, y, z + 1)); 178 | if (mult) { 179 | isActive(x, y, z) = true; 180 | result.powerMult += mult * (modPower / 6.0); 181 | result.heatMult += mult * (modHeat / 6.0); 182 | } else if (!isModeratorInLine(x, y, z)) { 183 | result.invalidTiles.emplace_back(x, y, z); 184 | } 185 | } else switch (rules(x, y, z)) { 186 | case Redstone: 187 | isActive(x, y, z) = countNeighbors(Cell, x, y, z); 188 | break; 189 | case Lapis: 190 | isActive(x, y, z) = countNeighbors(Cell, x, y, z) 191 | && countCasingNeighbors(x, y, z); 192 | break; 193 | case Enderium: 194 | isActive(x, y, z) = countCasingNeighbors(x, y, z) == 3 195 | && (!x || x == settings.sizeX - 1) 196 | && (!y || y == settings.sizeY - 1) 197 | && (!z || z == settings.sizeZ - 1); 198 | break; 199 | case Cryotheum: 200 | isActive(x, y, z) = countNeighbors(Cell, x, y, z) >= 2; 201 | } 202 | } 203 | } 204 | } 205 | 206 | for (int x{}; x < settings.sizeX; ++x) { 207 | for (int y{}; y < settings.sizeY; ++y) { 208 | for (int z{}; z < settings.sizeZ; ++z) { 209 | switch (rules(x, y, z)) { 210 | case Water: 211 | isActive(x, y, z) = countNeighbors(Cell, x, y, z) 212 | || countActiveNeighbors(Moderator, x, y, z); 213 | break; 214 | case Quartz: 215 | isActive(x, y, z) = countActiveNeighbors(Moderator, x, y, z); 216 | break; 217 | case Glowstone: 218 | isActive(x, y, z) = countActiveNeighbors(Moderator, x, y, z) >= 2; 219 | break; 220 | case Helium: 221 | isActive(x, y, z) = countActiveNeighbors(Redstone, x, y, z) == 1 222 | && countCasingNeighbors(x, y, z); 223 | break; 224 | case Emerald: 225 | isActive(x, y, z) = countActiveNeighbors(Moderator, x, y, z) 226 | && countNeighbors(Cell, x, y, z); 227 | break; 228 | case Tin: 229 | isActive(x, y, z) = 230 | isActiveSafe(Lapis, x - 1, y, z) && 231 | isActiveSafe(Lapis, x + 1, y, z) || 232 | isActiveSafe(Lapis, x, y - 1, z) && 233 | isActiveSafe(Lapis, x, y + 1, z) || 234 | isActiveSafe(Lapis, x, y, z - 1) && 235 | isActiveSafe(Lapis, x, y, z + 1); 236 | break; 237 | case Magnesium: 238 | isActive(x, y, z) = countActiveNeighbors(Moderator, x, y, z) 239 | && countCasingNeighbors(x, y, z); 240 | } 241 | } 242 | } 243 | } 244 | 245 | for (int x{}; x < settings.sizeX; ++x) { 246 | for (int y{}; y < settings.sizeY; ++y) { 247 | for (int z{}; z < settings.sizeZ; ++z) { 248 | switch (rules(x, y, z)) { 249 | case Gold: 250 | isActive(x, y, z) = countActiveNeighbors(Water, x, y, z) 251 | && countActiveNeighbors(Redstone, x, y, z); 252 | break; 253 | case Diamond: 254 | isActive(x, y, z) = countActiveNeighbors(Water, x, y, z) 255 | && countActiveNeighbors(Quartz, x, y, z); 256 | break; 257 | case Copper: 258 | isActive(x, y, z) = countActiveNeighbors(Glowstone, x, y, z); 259 | } 260 | } 261 | } 262 | } 263 | 264 | for (int x{}; x < settings.sizeX; ++x) { 265 | for (int y{}; y < settings.sizeY; ++y) { 266 | for (int z{}; z < settings.sizeZ; ++z) { 267 | int tile((*this->state)(x, y, z)); 268 | if (tile < Cell) { 269 | if (rules(x, y, z) == Iron) 270 | isActive(x, y, z) = countActiveNeighbors(Gold, x, y, z); 271 | if (isActive(x, y, z)) 272 | result.cooling += settings.coolingRates[tile]; 273 | else 274 | result.invalidTiles.emplace_back(x, y, z); 275 | } 276 | } 277 | } 278 | } 279 | 280 | result.compute(settings); 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /Fission.h: -------------------------------------------------------------------------------- 1 | #ifndef _FISSION_H_ 2 | #define _FISSION_H_ 3 | #include 4 | #include 5 | 6 | namespace Fission { 7 | using Coords = std::vector>; 8 | 9 | constexpr int neutronReach(4); 10 | constexpr double modPower(1.0), modHeat(2.0); 11 | 12 | enum { 13 | // Cooler 14 | Water, Redstone, Quartz, Gold, Glowstone, 15 | Lapis, Diamond, Helium, Enderium, Cryotheum, 16 | Iron, Emerald, Copper, Tin, Magnesium, Active, 17 | // Other 18 | Cell = Active * 2, Moderator, Air 19 | }; 20 | 21 | enum { 22 | GoalPower, 23 | GoalBreeder, 24 | GoalEfficiency 25 | }; 26 | 27 | struct Settings { 28 | int sizeX, sizeY, sizeZ; 29 | double fuelBasePower, fuelBaseHeat; 30 | int limit[Air]; 31 | double coolingRates[Cell]; 32 | bool ensureActiveCoolerAccessible; 33 | bool ensureHeatNeutral; 34 | int goal; 35 | bool symX, symY, symZ; 36 | }; 37 | 38 | struct Evaluation { 39 | // Raw 40 | Coords invalidTiles; 41 | double powerMult, heatMult, cooling; 42 | int breed; 43 | // Computed 44 | double heat, netHeat, dutyCycle, avgMult, power, avgPower, avgBreed, efficiency; 45 | 46 | void compute(const Settings &settings); 47 | }; 48 | 49 | class Evaluator { 50 | const Settings &settings; 51 | xt::xtensor mults, rules; 52 | xt::xtensor isActive, isModeratorInLine, visited; 53 | const xt::xtensor *state; 54 | int compatibleTile; 55 | 56 | int getTileSafe(int x, int y, int z) const; 57 | int getMultSafe(int x, int y, int z) const; 58 | bool countMult(int x, int y, int z, int dx, int dy, int dz); 59 | int countMult(int x, int y, int z); 60 | bool isActiveSafe(int tile, int x, int y, int z) const; 61 | int countActiveNeighbors(int tile, int x, int y, int z) const; 62 | bool isTileSafe(int tile, int x, int y, int z) const; 63 | int countNeighbors(int tile, int x, int y, int z) const; 64 | int countCasingNeighbors(int x, int y, int z) const; 65 | bool checkAccessibility(int compatibleTile, int x, int y, int z); 66 | bool checkAccessibility(int x, int y, int z); 67 | public: 68 | Evaluator(const Settings &settings); 69 | void run(const xt::xtensor &state, Evaluation &result); 70 | }; 71 | } 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /FissionNet.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "FissionNet.h" 3 | 4 | namespace Fission { 5 | Net::Net(Opt &opt) :opt(opt), mCorrector(1), rCorrector(1), trajectoryLength(), writePos() { 6 | for (int i{}; i < Air; ++i) 7 | if (opt.settings.limit[i]) 8 | tileMap.emplace(i, tileMap.size()); 9 | tileMap.emplace(Air, tileMap.size()); 10 | nFeatures = static_cast(tileMap.size() * 2 - 1 + nStatisticalFeatures); 11 | batchInput = xt::empty({nMiniBatch, nFeatures}); 12 | batchTarget = xt::empty({nMiniBatch}); 13 | 14 | wLayer1 = xt::random::randn({nLayer1, nFeatures}, 0.0, 1.0 / std::sqrt(nFeatures), opt.rng); 15 | mwLayer1 = xt::zeros_like(wLayer1); 16 | rwLayer1 = xt::zeros_like(wLayer1); 17 | bLayer1 = xt::zeros({nLayer1}); 18 | mbLayer1 = xt::zeros_like(bLayer1); 19 | rbLayer1 = xt::zeros_like(bLayer1); 20 | 21 | wLayer2 = xt::random::randn({nLayer2, nLayer1}, 0.0, 1.0 / std::sqrt(nLayer1), opt.rng); 22 | mwLayer2 = xt::zeros_like(wLayer2); 23 | rwLayer2 = xt::zeros_like(wLayer2); 24 | bLayer2 = xt::zeros({nLayer2}); 25 | mbLayer2 = xt::zeros_like(bLayer2); 26 | rbLayer2 = xt::zeros_like(bLayer2); 27 | 28 | wOutput = xt::random::randn({nLayer2}, 0.0, 1.0 / std::sqrt(nLayer2), opt.rng); 29 | mwOutput = xt::zeros_like(wOutput); 30 | rwOutput = xt::zeros_like(wOutput); 31 | bOutput = 0.0; 32 | mbOutput = 0.0; 33 | rbOutput = 0.0; 34 | } 35 | 36 | void Net::appendTrajectory(const Sample &sample) { 37 | ++trajectoryLength; 38 | if (pool.size() == nPool) 39 | pool[writePos].first = extractFeatures(sample); 40 | else 41 | pool.emplace_back(extractFeatures(sample), 0.0); 42 | if (++writePos == nPool) 43 | writePos = 0; 44 | } 45 | 46 | void Net::finishTrajectory(double target) { 47 | int pos(writePos); 48 | for (int i{}; i < trajectoryLength; ++i) { 49 | if (--pos < 0) 50 | pos = nPool - 1; 51 | pool[pos].second = target; 52 | } 53 | } 54 | 55 | xt::xtensor Net::extractFeatures(const Sample &sample) { 56 | xt::xtensor vInput(xt::zeros({nFeatures})); 57 | for (int x{}; x < opt.settings.sizeX; ++x) 58 | for (int y{}; y < opt.settings.sizeY; ++y) 59 | for (int z{}; z < opt.settings.sizeZ; ++z) 60 | ++vInput[tileMap[sample.state(x, y, z)]]; 61 | for (auto &[x, y, z] : sample.value.invalidTiles) 62 | ++vInput[tileMap.size() + tileMap[sample.state(x, y, z)]]; 63 | vInput.periodic(-1) = sample.value.powerMult; 64 | vInput.periodic(-2) = sample.value.heatMult; 65 | vInput.periodic(-3) = sample.value.cooling / opt.settings.fuelBaseHeat; 66 | vInput /= opt.settings.sizeX * opt.settings.sizeY * opt.settings.sizeZ; 67 | vInput.periodic(-4) = sample.value.dutyCycle; 68 | vInput.periodic(-5) = sample.value.efficiency; 69 | return vInput; 70 | } 71 | 72 | double Net::infer(const Sample &sample) { 73 | auto vInput(extractFeatures(sample)); 74 | xt::xtensor vLayer1(bLayer1 + xt::sum(wLayer1 * vInput, -1)); 75 | xt::xtensor vPwlLayer1(vLayer1 * leak + xt::clip(vLayer1, -1.0, 1.0)); 76 | xt::xtensor vLayer2(bLayer2 + xt::sum(wLayer2 * vPwlLayer1, -1)); 77 | xt::xtensor vPwlLayer2(vLayer2 * leak + xt::clip(vLayer2, -1.0, 1.0)); 78 | return bOutput + xt::sum(wOutput * vPwlLayer2)(); 79 | } 80 | 81 | double Net::train() { 82 | // Assemble batch 83 | std::uniform_int_distribution dist(0, pool.size() - 1); 84 | for (int i{}; i < nMiniBatch; ++i) { 85 | auto &sample(pool[dist(opt.rng)]); 86 | xt::view(batchInput, i, xt::all()) = sample.first; 87 | batchTarget(i) = sample.second; 88 | } 89 | 90 | // Forward 91 | xt::xtensor vLayer1(bLayer1 + xt::sum(wLayer1 * xt::view(batchInput, xt::all(), xt::newaxis(), xt::all()), -1)); 92 | xt::xtensor vPwlLayer1(vLayer1 * leak + xt::clip(vLayer1, -1.0, 1.0)); 93 | xt::xtensor vLayer2(bLayer2 + xt::sum(wLayer2 * xt::view(vPwlLayer1, xt::all(), xt::newaxis(), xt::all()), -1)); 94 | xt::xtensor vPwlLayer2(vLayer2 * leak + xt::clip(vLayer2, -1.0, 1.0)); 95 | xt::xtensor vOutput(bOutput + xt::sum(wOutput * vPwlLayer2, -1)); 96 | xt::xtensor losses(xt::square(vOutput - batchTarget)); 97 | double loss(xt::mean(losses)()); 98 | 99 | // Backward 100 | xt::xtensor gvOutput((vOutput - batchTarget) * 2 / nMiniBatch); 101 | double gbOutput(xt::sum(gvOutput)()); 102 | xt::xtensor gwOutput(xt::sum(xt::view(gvOutput, xt::all(), xt::newaxis()) * vPwlLayer2, 0)); 103 | xt::xtensor gvPwlLayer2(xt::empty_like(vPwlLayer2)); 104 | for (int i{}; i < nMiniBatch; ++i) 105 | for (int j{}; j < nLayer2; ++j) 106 | gvPwlLayer2(i, j) = gvOutput(i) * wOutput(j); 107 | xt::xtensor gvLayer2(gvPwlLayer2 * (leak + (xt::abs(vLayer2) < 1.0))); 108 | xt::xtensor gbLayer2(xt::sum(gvLayer2, 0)); 109 | xt::xtensor gwLayer2(xt::empty_like(wLayer2)); 110 | for (int i{}; i < nLayer2; ++i) 111 | for (int j{}; j < nLayer1; ++j) 112 | gwLayer2(i, j) = xt::sum(xt::view(gvLayer2, xt::all(), i) * xt::view(vPwlLayer1, xt::all(), j))(); 113 | xt::xtensor gvPwlLayer1(xt::sum(xt::view(gvLayer2, xt::all(), xt::all(), xt::newaxis()) * wLayer2, -2)); 114 | xt::xtensor gvLayer1(gvPwlLayer1 * (leak + (xt::abs(vLayer1) < 1.0))); 115 | xt::xtensor gbLayer1(xt::sum(gvLayer1, 0)); 116 | xt::xtensor gwLayer1(xt::empty_like(wLayer1)); 117 | for (int i{}; i < nLayer1; ++i) 118 | for (int j{}; j < nFeatures; ++j) 119 | gwLayer1(i, j) = xt::sum(xt::view(gvLayer1, xt::all(), i) * xt::view(batchInput, xt::all(), j))(); 120 | 121 | // Adam 122 | mCorrector *= mRate; 123 | mwLayer1 = mRate * mwLayer1 + (1 - mRate) * gwLayer1; 124 | mbLayer1 = mRate * mbLayer1 + (1 - mRate) * gbLayer1; 125 | mwLayer2 = mRate * mwLayer2 + (1 - mRate) * gwLayer2; 126 | mbLayer2 = mRate * mbLayer2 + (1 - mRate) * gbLayer2; 127 | mwOutput = mRate * mwOutput + (1 - mRate) * gwOutput; 128 | mbOutput = mRate * mbOutput + (1 - mRate) * gbOutput; 129 | 130 | rCorrector *= rRate; 131 | rwLayer1 = rRate * rwLayer1 + (1 - rRate) * xt::square(gwLayer1); 132 | rbLayer1 = rRate * rbLayer1 + (1 - rRate) * xt::square(gbLayer1); 133 | rwLayer2 = rRate * rwLayer2 + (1 - rRate) * xt::square(gwLayer2); 134 | rbLayer2 = rRate * rbLayer2 + (1 - rRate) * xt::square(gbLayer2); 135 | rwOutput = rRate * rwOutput + (1 - rRate) * xt::square(gwOutput); 136 | rbOutput = rRate * rbOutput + (1 - rRate) * (gbOutput * gbOutput); 137 | 138 | wLayer1 -= lRate * mwLayer1 / ((1 - mCorrector) * (xt::sqrt(rwLayer1 / (1 - rCorrector)) + 1e-8)); 139 | bLayer1 -= lRate * mbLayer1 / ((1 - mCorrector) * (xt::sqrt(rbLayer1 / (1 - rCorrector)) + 1e-8)); 140 | wLayer2 -= lRate * mwLayer2 / ((1 - mCorrector) * (xt::sqrt(rwLayer2 / (1 - rCorrector)) + 1e-8)); 141 | bLayer2 -= lRate * mbLayer2 / ((1 - mCorrector) * (xt::sqrt(rbLayer2 / (1 - rCorrector)) + 1e-8)); 142 | wOutput -= lRate * mwOutput / ((1 - mCorrector) * (xt::sqrt(rwOutput / (1 - rCorrector)) + 1e-8)); 143 | bOutput -= lRate * mbOutput / ((1 - mCorrector) * (std::sqrt(rbOutput / (1 - rCorrector)) + 1e-8)); 144 | 145 | return loss; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /FissionNet.h: -------------------------------------------------------------------------------- 1 | #ifndef _FISSION_NET_H_ 2 | #define _FISSION_NET_H_ 3 | #include 4 | #include "OptFission.h" 5 | 6 | namespace Fission { 7 | constexpr int nStatisticalFeatures(5), nLayer1(128), nLayer2(64), nMiniBatch(64), nEpoch(2), nPool(1'000'000); 8 | constexpr double lRate(0.01), mRate(0.9), rRate(0.999), leak(0.1); 9 | 10 | class Net { 11 | Opt &opt; 12 | double mCorrector, rCorrector; 13 | std::unordered_map tileMap; 14 | int nFeatures; 15 | 16 | // Data Pool 17 | xt::xtensor batchInput; 18 | xt::xtensor batchTarget; 19 | std::vector, double>> pool; 20 | int trajectoryLength, writePos; 21 | 22 | xt::xtensor wLayer1, mwLayer1, rwLayer1; 23 | xt::xtensor bLayer1, mbLayer1, rbLayer1; 24 | xt::xtensor wLayer2, mwLayer2, rwLayer2; 25 | xt::xtensor bLayer2, mbLayer2, rbLayer2; 26 | xt::xtensor wOutput, mwOutput, rwOutput; 27 | double bOutput, mbOutput, rbOutput; 28 | 29 | xt::xtensor extractFeatures(const Sample &sample); 30 | public: 31 | Net(Opt &opt); 32 | double infer(const Sample &sample); 33 | void newTrajectory() { trajectoryLength = 0; } 34 | void appendTrajectory(const Sample &sample); 35 | void finishTrajectory(double target); 36 | int getTrajectoryLength() const { return trajectoryLength; } 37 | double train(); 38 | }; 39 | } 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /OptFission.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "FissionNet.h" 3 | 4 | namespace Fission { 5 | void Opt::restart() { 6 | std::shuffle(allowedCoords.begin(), allowedCoords.end(), rng); 7 | std::copy(settings.limit, settings.limit + Air, parent.limit); 8 | parent.state = xt::broadcast(Air, 9 | {settings.sizeX, settings.sizeY, settings.sizeZ}); 10 | for (auto &[x, y, z] : allowedCoords) { 11 | int nSym(getNSym(x, y, z)); 12 | allowedTiles.clear(); 13 | for (int tile{}; tile < Air; ++tile) 14 | if (parent.limit[tile] < 0 || parent.limit[tile] >= nSym) 15 | allowedTiles.emplace_back(tile); 16 | if (allowedTiles.empty()) 17 | break; 18 | int newTile(allowedTiles[std::uniform_int_distribution<>(0, static_cast(allowedTiles.size() - 1))(rng)]); 19 | parent.limit[newTile] -= nSym; 20 | setTileWithSym(parent, x, y, z, newTile); 21 | } 22 | evaluator.run(parent.state, parent.value); 23 | } 24 | 25 | Opt::Opt(const Settings &settings, bool useNet) 26 | :settings(settings), evaluator(settings), 27 | nEpisode(), nStage(), nIteration(), nConverge(), 28 | maxConverge(std::min(7 * 7 * 7, settings.sizeX * settings.sizeY * settings.sizeZ) * 16), 29 | infeasibilityPenalty(), bestChanged(true), redrawNagle(), lossHistory(nLossHistory), lossChanged() { 30 | for (int x(settings.symX ? settings.sizeX / 2 : 0); x < settings.sizeX; ++x) 31 | for (int y(settings.symY ? settings.sizeY / 2 : 0); y < settings.sizeY; ++y) 32 | for (int z(settings.symZ ? settings.sizeZ / 2 : 0); z < settings.sizeZ; ++z) 33 | allowedCoords.emplace_back(x, y, z); 34 | 35 | restart(); 36 | if (useNet) { 37 | net = std::make_unique(*this); 38 | net->appendTrajectory(parent); 39 | } 40 | parentFitness = currentFitness(parent); 41 | 42 | best.state = xt::broadcast(Air, 43 | {settings.sizeX, settings.sizeY, settings.sizeZ}); 44 | evaluator.run(best.state, best.value); 45 | } 46 | 47 | bool Opt::feasible(const Evaluation &x) { 48 | return !settings.ensureHeatNeutral || x.netHeat <= 0.0; 49 | } 50 | 51 | double Opt::rawFitness(const Evaluation &x) { 52 | switch (settings.goal) { 53 | default: // GoalPower 54 | return x.avgMult; 55 | case GoalBreeder: 56 | return x.avgBreed; 57 | case GoalEfficiency: 58 | return settings.ensureHeatNeutral ? (x.efficiency - 1) * x.dutyCycle : x.efficiency - 1; 59 | } 60 | } 61 | 62 | double Opt::currentFitness(const Sample &x) { 63 | if (nStage == StageInfer) { 64 | return net->infer(x); 65 | } else if (nStage == StageTrain) { 66 | return 0.0; 67 | } else if (feasible(x.value)) { 68 | return rawFitness(x.value); 69 | } else { 70 | return rawFitness(x.value) - x.value.netHeat / settings.fuelBaseHeat * infeasibilityPenalty; 71 | } 72 | } 73 | 74 | int Opt::getNSym(int x, int y, int z) { 75 | int result(1); 76 | if (settings.symX && x != settings.sizeX - x - 1) 77 | result *= 2; 78 | if (settings.symY && y != settings.sizeY - y - 1) 79 | result *= 2; 80 | if (settings.symZ && z != settings.sizeZ - z - 1) 81 | result *= 2; 82 | return result; 83 | } 84 | 85 | void Opt::setTileWithSym(Sample &sample, int x, int y, int z, int tile) { 86 | sample.state(x, y, z) = tile; 87 | if (settings.symX) { 88 | sample.state(settings.sizeX - x - 1, y, z) = tile; 89 | if (settings.symY) { 90 | sample.state(x, settings.sizeY - y - 1, z) = tile; 91 | sample.state(settings.sizeX - x - 1, settings.sizeY - y - 1, z) = tile; 92 | if (settings.symZ) { 93 | sample.state(x, y, settings.sizeZ - z - 1) = tile; 94 | sample.state(settings.sizeX - x - 1, y, settings.sizeZ - z - 1) = tile; 95 | sample.state(x, settings.sizeY - y - 1, settings.sizeZ - z - 1) = tile; 96 | sample.state(settings.sizeX - x - 1, settings.sizeY - y - 1, settings.sizeZ - z - 1) = tile; 97 | } 98 | } else if (settings.symZ) { 99 | sample.state(x, y, settings.sizeZ - z - 1) = tile; 100 | sample.state(settings.sizeX - x - 1, y, settings.sizeZ - z - 1) = tile; 101 | } 102 | } else if (settings.symY) { 103 | sample.state(x, settings.sizeY - y - 1, z) = tile; 104 | if (settings.symZ) { 105 | sample.state(x, y, settings.sizeZ - z - 1) = tile; 106 | sample.state(x, settings.sizeY - y - 1, settings.sizeZ - z - 1) = tile; 107 | } 108 | } else if (settings.symZ) { 109 | sample.state(x, y, settings.sizeZ - z - 1) = tile; 110 | } 111 | } 112 | 113 | void Opt::mutateAndEvaluate(Sample &sample, int x, int y, int z) { 114 | int nSym(getNSym(x, y, z)); 115 | int oldTile(sample.state(x, y, z)); 116 | if (oldTile != Air) 117 | sample.limit[oldTile] += nSym; 118 | allowedTiles.clear(); 119 | allowedTiles.emplace_back(Air); 120 | for (int tile{}; tile < Air; ++tile) 121 | if (sample.limit[tile] < 0 || sample.limit[tile] >= nSym) 122 | allowedTiles.emplace_back(tile); 123 | int newTile(allowedTiles[std::uniform_int_distribution<>(0, static_cast(allowedTiles.size() - 1))(rng)]); 124 | if (newTile != Air) 125 | sample.limit[newTile] -= nSym; 126 | setTileWithSym(sample, x, y, z, newTile); 127 | evaluator.run(sample.state, sample.value); 128 | } 129 | 130 | void Opt::step() { 131 | if (nStage == StageTrain) { 132 | if (!nIteration) { 133 | nStage = StageInfer; 134 | parentFitness = net->infer(parent); 135 | inferenceFailed = true; 136 | } else { 137 | for (int i{}; i < nLossHistory - 1; ++i) 138 | lossHistory[i] = lossHistory[i + 1]; 139 | lossHistory[nLossHistory - 1] = net->train(); 140 | lossChanged = true; 141 | --nIteration; 142 | return; 143 | } 144 | } 145 | 146 | if (nConverge == maxConverge) { 147 | nIteration = 0; 148 | nConverge = 0; 149 | if (nStage == StageInfer) { 150 | nStage = 0; 151 | ++nEpisode; 152 | if (inferenceFailed) 153 | restart(); 154 | net->newTrajectory(); 155 | net->appendTrajectory(parent); 156 | } else if (feasible(parent.value) || infeasibilityPenalty > 1e8) { 157 | infeasibilityPenalty = 0.0; 158 | if (net) { 159 | nStage = StageTrain; 160 | net->finishTrajectory(feasible(parent.value) ? rawFitness(parent.value) : 0.0); 161 | nIteration = (net->getTrajectoryLength() * nEpoch + nMiniBatch - 1) / nMiniBatch; 162 | return; 163 | } else { 164 | nStage = 0; 165 | ++nEpisode; 166 | restart(); 167 | } 168 | } else { 169 | ++nStage; 170 | if (infeasibilityPenalty) 171 | infeasibilityPenalty *= 2; 172 | else 173 | infeasibilityPenalty = std::uniform_real_distribution<>()(rng); 174 | } 175 | parentFitness = currentFitness(parent); 176 | } 177 | 178 | bool bestChangedLocal(!nEpisode && !nStage && !nIteration && feasible(parent.value)); 179 | if (bestChangedLocal) 180 | best = parent; 181 | std::uniform_int_distribution<> 182 | xDist(0, settings.sizeX - 1), 183 | yDist(0, settings.sizeY - 1), 184 | zDist(0, settings.sizeZ - 1); 185 | int bestChild; 186 | double bestFitness; 187 | for (int i{}; i < children.size(); ++i) { 188 | auto &child(children[i]); 189 | child.state = parent.state; 190 | std::copy(parent.limit, parent.limit + Air, child.limit); 191 | mutateAndEvaluate(child, xDist(rng), yDist(rng), zDist(rng)); 192 | double fitness(currentFitness(child)); 193 | if (!i || fitness > bestFitness) { 194 | bestChild = i; 195 | bestFitness = fitness; 196 | } 197 | if (feasible(child.value) && rawFitness(child.value) > rawFitness(best.value)) { 198 | bestChangedLocal = true; 199 | best = child; 200 | } 201 | } 202 | auto &child(children[bestChild]); 203 | if (bestFitness >= parentFitness) { 204 | if (bestFitness > parentFitness) { 205 | parentFitness = bestFitness; 206 | nConverge = 0; 207 | if (nStage == StageInfer) 208 | inferenceFailed = false; 209 | } 210 | std::swap(parent, child); 211 | if (net && nStage != StageInfer) 212 | net->appendTrajectory(parent); 213 | } 214 | ++nConverge; 215 | ++nIteration; 216 | if (bestChangedLocal) { 217 | for (auto &[x, y, z] : best.value.invalidTiles) 218 | best.state(x, y, z) = Air; 219 | bestChanged = true; 220 | } 221 | } 222 | 223 | void Opt::stepInteractive() { 224 | int dim(settings.sizeX * settings.sizeY * settings.sizeZ); 225 | int n(std::min(interactiveMin, (interactiveScale + dim - 1) / dim)); 226 | for (int i{}; i < (nStage == StageTrain ? interactiveNet : nStage == StageInfer ? interactiveNet * nMiniBatch / 4 : n); ++i) { 227 | step(); 228 | ++redrawNagle; 229 | } 230 | } 231 | 232 | bool Opt::needsRedrawBest() { 233 | bool result(bestChanged && redrawNagle >= interactiveMin); 234 | if (result) { 235 | bestChanged = false; 236 | redrawNagle = 0; 237 | } 238 | return result; 239 | } 240 | 241 | bool Opt::needsReplotLoss() { 242 | bool result(lossChanged); 243 | if (result) 244 | lossChanged = false; 245 | return result; 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /OptFission.h: -------------------------------------------------------------------------------- 1 | #ifndef _OPT_FISSION_H_ 2 | #define _OPT_FISSION_H_ 3 | #include 4 | #include 5 | #include "Fission.h" 6 | 7 | namespace Fission { 8 | struct Sample { 9 | int limit[Air]; 10 | xt::xtensor state; 11 | Evaluation value; 12 | }; 13 | 14 | enum { 15 | StageTrain = -2, 16 | StageInfer 17 | }; 18 | 19 | constexpr int interactiveMin(1024), interactiveScale(327680), interactiveNet(16), nLossHistory(256); 20 | 21 | class Net; 22 | 23 | class Opt { 24 | friend Net; 25 | const Settings &settings; 26 | Evaluator evaluator; 27 | Coords allowedCoords; 28 | std::vector allowedTiles; 29 | int nEpisode, nStage, nIteration; 30 | int nConverge, maxConverge; 31 | double infeasibilityPenalty; 32 | double parentFitness; 33 | Sample parent, best; 34 | std::array children; 35 | std::mt19937 rng; 36 | std::unique_ptr net; 37 | bool inferenceFailed; 38 | bool bestChanged; 39 | int redrawNagle; 40 | std::vector lossHistory; 41 | bool lossChanged; 42 | void restart(); 43 | bool feasible(const Evaluation &x); 44 | double rawFitness(const Evaluation &x); 45 | double currentFitness(const Sample &x); 46 | int getNSym(int x, int y, int z); 47 | void setTileWithSym(Sample &sample, int x, int y, int z, int tile); 48 | void mutateAndEvaluate(Sample &sample, int x, int y, int z); 49 | public: 50 | Opt(const Settings &settings, bool useNet); 51 | void step(); 52 | void stepInteractive(); 53 | bool needsRedrawBest(); 54 | bool needsReplotLoss(); 55 | const std::vector &getLossHistory() const { return lossHistory; } 56 | const Sample &getBest() const { return best; } 57 | int getNEpisode() const { return nEpisode; } 58 | int getNStage() const { return nStage; } 59 | int getNIteration() const { return nIteration; } 60 | }; 61 | } 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /OptOverhaulFission.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "OverhaulFissionNet.h" 4 | 5 | namespace OverhaulFission { 6 | void Opt::restart() { 7 | std::shuffle(allowedCoords.begin(), allowedCoords.end(), rng); 8 | std::copy(settings.limits, settings.limits + Tiles::Air, parent.limits); 9 | std::copy(settings.sourceLimits, settings.sourceLimits + 3, parent.sourceLimits); 10 | parent.cellLimits.clear(); 11 | for (auto &fuel : settings.fuels) 12 | parent.cellLimits.emplace_back(fuel.limit); 13 | parent.state = xt::broadcast(Tiles::Air, 14 | {settings.sizeX, settings.sizeY, settings.sizeZ}); 15 | for (auto &[x, y, z] : allowedCoords) { 16 | int nSym(getNSym(x, y, z)); 17 | allowedTiles.clear(); 18 | for (int tile{}; tile < Tiles::Air; ++tile) 19 | if (parent.limits[tile] < 0 || parent.limits[tile] >= nSym) 20 | allowedTiles.emplace_back(tile); 21 | for (int cell{}; cell < static_cast(settings.cellTypes.size()); ++cell) { 22 | auto &[fuel, source](settings.cellTypes[cell]); 23 | if (parent.cellLimits[fuel] >= 0 && parent.cellLimits[fuel] < nSym) 24 | continue; 25 | if (source && parent.sourceLimits[source - 1] >= 0 && parent.sourceLimits[source - 1] < nSym) 26 | continue; 27 | allowedTiles.emplace_back(Tiles::C0 + cell); 28 | } 29 | if (allowedTiles.empty()) 30 | break; 31 | int newTile(allowedTiles[std::uniform_int_distribution<>(0, static_cast(allowedTiles.size() - 1))(rng)]); 32 | if (newTile < Tiles::Air) { 33 | parent.limits[newTile] -= nSym; 34 | } else { 35 | auto &[fuel, source](settings.cellTypes[newTile - Tiles::C0]); 36 | parent.cellLimits[fuel] -= nSym; 37 | if (source) 38 | parent.sourceLimits[source - 1] -= nSym; 39 | } 40 | setTileWithSym(parent, x, y, z, newTile); 41 | } 42 | parent.value.run(parent.state); 43 | if (settings.controllable) 44 | parent.valueWithShield.run(parent.state); 45 | } 46 | 47 | Opt::Opt(Settings &settings) 48 | :settings(settings), 49 | nEpisode(), nStage(StageRollout), nIteration(), nConverge(), 50 | penalty(xt::ones({nConstraints})), 51 | hasFeasible(xt::zeros({nConstraints})), 52 | hasInfeasible(xt::zeros({nConstraints})), 53 | bestChanged(true), redrawNagle(), lossHistory(nLossHistory), lossChanged() { 54 | settings.compute(); 55 | for (int x(settings.symX ? settings.sizeX / 2 : 0); x < settings.sizeX; ++x) 56 | for (int y(settings.symY ? settings.sizeY / 2 : 0); y < settings.sizeY; ++y) 57 | for (int z(settings.symZ ? settings.sizeZ / 2 : 0); z < settings.sizeZ; ++z) 58 | allowedCoords.emplace_back(x, y, z); 59 | 60 | parent.value.initialize(settings, false); 61 | if (settings.controllable) 62 | parent.valueWithShield.initialize(settings, true); 63 | restart(); 64 | net = std::make_unique(*this); 65 | net->appendTrajectory(net->extractFeatures(parent)); 66 | parentFitness = currentFitness(parent); 67 | localBest = xt::all(feasible(parent)) ? parentFitness : 0.0; 68 | 69 | child.value.initialize(settings, false); 70 | if (settings.controllable) 71 | child.valueWithShield.initialize(settings, true); 72 | 73 | best.state = xt::broadcast(Tiles::Air, 74 | {settings.sizeX, settings.sizeY, settings.sizeZ}); 75 | best.value.initialize(settings, false); 76 | best.value.run(best.state); 77 | } 78 | 79 | xt::xtensor Opt::feasible(const Sample &x) { 80 | return { 81 | !x.value.totalPositiveNetHeat, 82 | !settings.controllable || !x.valueWithShield.nActiveCells 83 | }; 84 | } 85 | 86 | xt::xtensor Opt::infeasibility(const Sample &x) { 87 | return { 88 | static_cast(x.value.totalPositiveNetHeat) / settings.minHeat, 89 | settings.controllable ? x.valueWithShield.nActiveCells : 0.0 90 | }; 91 | } 92 | 93 | double Opt::rawFitness(const Evaluation &x) { 94 | switch (settings.goal) { 95 | default: // GoalOutput 96 | return x.output / settings.maxOutput; 97 | case GoalFuelUse: 98 | return x.nActiveCells; 99 | case GoalEfficiency: 100 | return x.efficiency; 101 | case GoalIrradiation: 102 | return static_cast(x.irradiatorFlux) / settings.minCriticality; 103 | } 104 | } 105 | 106 | double Opt::currentFitness(const Sample &x) { 107 | if (nStage == StageInfer) { 108 | return net->infer(x); 109 | } else if (nStage == StageTrain) { 110 | return 0.0; 111 | } else { 112 | double result(rawFitness(x.value)); 113 | result += std::min(x.value.totalRawFlux, settings.minCriticality) / static_cast(settings.minCriticality); 114 | result += std::min(x.value.maxCellFlux, settings.minCriticality) / static_cast(settings.minCriticality); 115 | result -= xt::sum(infeasibility(x) * penalty)(); 116 | return result; 117 | } 118 | } 119 | 120 | int Opt::getNSym(int x, int y, int z) { 121 | int result(1); 122 | if (settings.symX && x != settings.sizeX - x - 1) 123 | result *= 2; 124 | if (settings.symY && y != settings.sizeY - y - 1) 125 | result *= 2; 126 | if (settings.symZ && z != settings.sizeZ - z - 1) 127 | result *= 2; 128 | return result; 129 | } 130 | 131 | void Opt::setTileWithSym(Sample &sample, int x, int y, int z, int tile) { 132 | sample.state(x, y, z) = tile; 133 | if (settings.symX) { 134 | sample.state(settings.sizeX - x - 1, y, z) = tile; 135 | if (settings.symY) { 136 | sample.state(x, settings.sizeY - y - 1, z) = tile; 137 | sample.state(settings.sizeX - x - 1, settings.sizeY - y - 1, z) = tile; 138 | if (settings.symZ) { 139 | sample.state(x, y, settings.sizeZ - z - 1) = tile; 140 | sample.state(settings.sizeX - x - 1, y, settings.sizeZ - z - 1) = tile; 141 | sample.state(x, settings.sizeY - y - 1, settings.sizeZ - z - 1) = tile; 142 | sample.state(settings.sizeX - x - 1, settings.sizeY - y - 1, settings.sizeZ - z - 1) = tile; 143 | } 144 | } else if (settings.symZ) { 145 | sample.state(x, y, settings.sizeZ - z - 1) = tile; 146 | sample.state(settings.sizeX - x - 1, y, settings.sizeZ - z - 1) = tile; 147 | } 148 | } else if (settings.symY) { 149 | sample.state(x, settings.sizeY - y - 1, z) = tile; 150 | if (settings.symZ) { 151 | sample.state(x, y, settings.sizeZ - z - 1) = tile; 152 | sample.state(x, settings.sizeY - y - 1, settings.sizeZ - z - 1) = tile; 153 | } 154 | } else if (settings.symZ) { 155 | sample.state(x, y, settings.sizeZ - z - 1) = tile; 156 | } 157 | } 158 | 159 | void Opt::mutateAndEvaluate(Sample &sample, int x, int y, int z) { 160 | int nSym(getNSym(x, y, z)); 161 | int oldTile(sample.state(x, y, z)); 162 | if (oldTile < Tiles::Air) { 163 | sample.limits[oldTile] += nSym; 164 | } else if (oldTile >= Tiles::C0) { 165 | auto &[fuel, source](settings.cellTypes[oldTile - Tiles::C0]); 166 | sample.cellLimits[fuel] += nSym; 167 | if (source) 168 | sample.sourceLimits[source - 1] += nSym; 169 | } 170 | allowedTiles.clear(); 171 | allowedTiles.emplace_back(Tiles::Air); 172 | for (int tile{}; tile < Tiles::Air; ++tile) 173 | if (sample.limits[tile] < 0 || sample.limits[tile] >= nSym) 174 | allowedTiles.emplace_back(tile); 175 | for (int cell{}; cell < static_cast(settings.cellTypes.size()); ++cell) { 176 | auto &[fuel, source](settings.cellTypes[cell]); 177 | if (sample.cellLimits[fuel] >= 0 && sample.cellLimits[fuel] < nSym) 178 | continue; 179 | if (source && sample.sourceLimits[source - 1] >= 0 && sample.sourceLimits[source - 1] < nSym) 180 | continue; 181 | allowedTiles.emplace_back(Tiles::C0 + cell); 182 | } 183 | int newTile(allowedTiles[std::uniform_int_distribution<>(0, static_cast(allowedTiles.size() - 1))(rng)]); 184 | if (newTile < Tiles::Air) { 185 | sample.limits[newTile] -= nSym; 186 | } else if (newTile >= Tiles::C0) { 187 | auto &[fuel, source](settings.cellTypes[newTile - Tiles::C0]); 188 | sample.cellLimits[fuel] -= nSym; 189 | if (source) 190 | sample.sourceLimits[source - 1] -= nSym; 191 | } 192 | setTileWithSym(sample, x, y, z, newTile); 193 | sample.value.run(sample.state); 194 | if (settings.controllable) 195 | sample.valueWithShield.run(sample.state); 196 | } 197 | 198 | void Opt::step() { 199 | if (nStage == StageTrain) { 200 | if (!nIteration) { 201 | nStage = StageInfer; 202 | restart(); 203 | parentFitness = currentFitness(parent); 204 | inferenceFailed = true; 205 | } else { 206 | for (int i{}; i < nLossHistory - 1; ++i) 207 | lossHistory[i] = lossHistory[i + 1]; 208 | lossHistory[nLossHistory - 1] = net->train(); 209 | lossChanged = true; 210 | --nIteration; 211 | return; 212 | } 213 | } else if (nStage == StageInfer) { 214 | if (nConverge == maxConvergeInfer) { 215 | nStage = StageRollout; 216 | ++nEpisode; 217 | std::cout << "episode " << nEpisode << std::endl; 218 | if (inferenceFailed) 219 | restart(); 220 | net->newTrajectory(); 221 | net->appendTrajectory(net->extractFeatures(parent)); 222 | parentFitness = currentFitness(parent); 223 | localBest = xt::all(feasible(parent)) ? parentFitness : 0.0; 224 | nConverge = 0; 225 | nIteration = 0; 226 | } 227 | } else if (nConverge == maxConvergeRollout) { 228 | nStage = StageTrain; 229 | trajectoryBuffer.clear(); 230 | net->finishTrajectory(localBest); 231 | nConverge = 0; 232 | nIteration = (net->getTrajectoryLength() * nEpoch + nMiniBatch - 1) / nMiniBatch; 233 | return; 234 | } 235 | 236 | bool bestChangedLocal(!nEpisode && nStage == StageRollout && !nIteration && xt::all(feasible(parent))); 237 | if (bestChangedLocal) 238 | best = parent; 239 | std::uniform_int_distribution<> 240 | xDist(0, settings.sizeX - 1), 241 | yDist(0, settings.sizeY - 1), 242 | zDist(0, settings.sizeZ - 1); 243 | child.state = parent.state; 244 | std::copy(parent.limits, parent.limits + Tiles::Air, child.limits); 245 | std::copy(parent.sourceLimits, parent.sourceLimits + 3, child.sourceLimits); 246 | child.cellLimits = parent.cellLimits; 247 | mutateAndEvaluate(child, xDist(rng), yDist(rng), zDist(rng)); 248 | double childFitness(currentFitness(child)); 249 | if (xt::all(feasible(child)) && rawFitness(child.value) > rawFitness(best.value)) { 250 | bestChangedLocal = true; 251 | best = child; 252 | } 253 | if (childFitness >= parentFitness) { 254 | if (childFitness > parentFitness) { 255 | parentFitness = childFitness; 256 | if (nStage == StageInfer) { 257 | nConverge = 0; 258 | inferenceFailed = false; 259 | } 260 | } 261 | if (nStage != StageInfer && !std::uniform_int_distribution<>(0, 9)(rng)) 262 | - trajectoryBuffer.emplace_back(net->extractFeatures(child)); 263 | std::swap(parent, child); 264 | } 265 | 266 | if (nStage != StageInfer) { 267 | auto feasible(this->feasible(parent)); 268 | if (xt::all(feasible)) { 269 | if (parentFitness > localBest) { 270 | localBest = parentFitness; 271 | std::cout << "localBest: " << localBest << std::endl; 272 | nConverge = 0; 273 | while (!trajectoryBuffer.empty()) { 274 | net->appendTrajectory(std::move(trajectoryBuffer.back())); 275 | trajectoryBuffer.pop_back(); 276 | } 277 | } 278 | } 279 | for (int i{}; i < nConstraints; ++i) 280 | if (feasible(i)) 281 | hasFeasible(i) = true; 282 | else 283 | hasInfeasible(i) = true; 284 | if (!(nIteration % penaltyUpdatePeriod)) { 285 | std::cout << penalty(0) << std::endl; 286 | for (int i{}; i < nConstraints; ++i) { 287 | if (hasFeasible(i) && !hasInfeasible(i)) 288 | penalty(i) *= 0.5; 289 | else if (!hasFeasible(i) && hasInfeasible(i)) 290 | penalty(i) = std::max(0.001, penalty(i) * 1.5); 291 | hasFeasible(i) = false; 292 | hasInfeasible(i) = false; 293 | } 294 | } 295 | parentFitness = currentFitness(parent); 296 | } 297 | 298 | ++nConverge; 299 | ++nIteration; 300 | if (bestChangedLocal) { 301 | best.value.canonicalize(best.state); 302 | bestChanged = true; 303 | } 304 | } 305 | 306 | void Opt::stepInteractive() { 307 | int dim(settings.sizeX * settings.sizeY * settings.sizeZ); 308 | int n(std::min(interactiveMin, (interactiveScale + dim - 1) / dim)); 309 | for (int i{}; i < (nStage == StageTrain ? interactiveNet : nStage == StageInfer ? interactiveNet * nMiniBatch : n); ++i) { 310 | step(); 311 | ++redrawNagle; 312 | } 313 | } 314 | 315 | bool Opt::needsRedrawBest() { 316 | bool result(bestChanged && redrawNagle >= interactiveMin); 317 | if (result) { 318 | bestChanged = false; 319 | redrawNagle = 0; 320 | } 321 | return result; 322 | } 323 | 324 | bool Opt::needsReplotLoss() { 325 | bool result(lossChanged); 326 | if (result) 327 | lossChanged = false; 328 | return result; 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /OptOverhaulFission.h: -------------------------------------------------------------------------------- 1 | #ifndef _OPT_OVERHAUL_FISSION_H_ 2 | #define _OPT_OVERHAUL_FISSION_H_ 3 | #include 4 | #include "OverhaulFission.h" 5 | 6 | namespace OverhaulFission { 7 | struct Sample { 8 | int limits[Tiles::Air]; 9 | int sourceLimits[3]; 10 | std::vector cellLimits; 11 | xt::xtensor state; 12 | Evaluation value, valueWithShield; 13 | }; 14 | 15 | enum { 16 | StageRollout, 17 | StageTrain, 18 | StageInfer 19 | }; 20 | 21 | constexpr int interactiveMin(4096), interactiveScale(327680), interactiveNet(4), nLossHistory(256); 22 | constexpr int maxConvergeInfer(10976), maxConvergeRollout(maxConvergeInfer * 100), nConstraints(2), penaltyUpdatePeriod(maxConvergeInfer); 23 | 24 | class Net; 25 | 26 | class Opt { 27 | friend Net; 28 | std::mt19937 rng; 29 | const Settings &settings; 30 | std::vector allowedCoords; 31 | std::vector allowedTiles; 32 | int nEpisode, nStage, nIteration; 33 | int nConverge; 34 | xt::xtensor hasFeasible, hasInfeasible; 35 | xt::xtensor penalty; 36 | std::vector> trajectoryBuffer; 37 | double parentFitness, localBest; 38 | Sample parent, child, best; 39 | std::unique_ptr net; 40 | bool inferenceFailed; 41 | bool bestChanged; 42 | int redrawNagle; 43 | std::vector lossHistory; 44 | bool lossChanged; 45 | void restart(); 46 | xt::xtensor feasible(const Sample &x); 47 | xt::xtensor infeasibility(const Sample &x); 48 | double rawFitness(const Evaluation &x); 49 | double currentFitness(const Sample &x); 50 | int getNSym(int x, int y, int z); 51 | void setTileWithSym(Sample &sample, int x, int y, int z, int tile); 52 | void mutateAndEvaluate(Sample &sample, int x, int y, int z); 53 | public: 54 | Opt(Settings &settings); 55 | void step(); 56 | void stepInteractive(); 57 | bool needsRedrawBest(); 58 | bool needsReplotLoss(); 59 | const std::vector &getLossHistory() const { return lossHistory; } 60 | const Sample &getBest() const { return best; } 61 | int getNEpisode() const { return nEpisode; } 62 | int getNStage() const { return nStage; } 63 | int getNIteration() const { return nIteration; } 64 | }; 65 | } 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /OverhaulFission.cpp: -------------------------------------------------------------------------------- 1 | #include "OverhaulFission.h" 2 | 3 | namespace OverhaulFission { 4 | const Coord directions[] { 5 | {-1, 0, 0}, 6 | {+1, 0, 0}, 7 | {0, -1, 0}, 8 | {0, +1, 0}, 9 | {0, 0, -1}, 10 | {0, 0, +1} 11 | }; 12 | 13 | void Settings::compute() { 14 | cellTypes.clear(); 15 | maxOutput = 0.0; 16 | minCriticality = INT_MAX; 17 | minHeat = INT_MAX; 18 | for (int i{}; i < static_cast(fuels.size()); ++i) { 19 | maxOutput = std::max(maxOutput, fuels[i].heat * fuels[i].efficiency); 20 | minCriticality = std::min(minCriticality, fuels[i].criticality); 21 | minHeat = std::min(minHeat, fuels[i].heat); 22 | cellTypes.emplace_back(i, 0); 23 | if (!fuels[i].selfPriming) { 24 | for (int j(1); j <= 3; ++j) { 25 | cellTypes.emplace_back(i, j); 26 | } 27 | } 28 | } 29 | } 30 | 31 | void Evaluation::initialize(const Settings &settings, bool shieldOn) { 32 | tiles = xt::empty({settings.sizeX, settings.sizeY, settings.sizeZ}); 33 | this->settings = &settings; 34 | this->shieldOn = shieldOn; 35 | } 36 | 37 | void Evaluation::checkNeutronSource(int x, int y, int z) { 38 | Cell &cell(*std::get_if(&tiles(x, y, z))); 39 | if (!cell.neutronSource) 40 | return; 41 | for (auto &[dx, dy, dz] : directions) { 42 | int cx(x), cy(y), cz(z); 43 | bool blocked{}; 44 | while (!blocked) { 45 | cx += dx; cy += dy; cz += dz; 46 | if (!tiles.in_bounds(cx, cy, cz)) 47 | return; 48 | std::visit(Overload { 49 | [&](Reflector &tile) { blocked = reflectorFluxMults[tile.type] >= 1.0; }, 50 | [&](Irradiator &tile) { blocked = true; }, 51 | [&](Cell &) { blocked = true; }, 52 | [](...) {} 53 | // Note: treating active shield as non-blocking because activating shield won't undo the flux activation. 54 | }, tiles(cx, cy, cz)); 55 | } 56 | } 57 | cell.neutronSource = 0; 58 | cell.isNeutronSourceBlocked = true; 59 | } 60 | 61 | void Evaluation::computeFluxEdge(int x, int y, int z) { 62 | Cell &cell(*std::get_if(&tiles(x, y, z))); 63 | for (int i{}; i < 6; ++i) { 64 | FluxEdge &edge(cell.fluxEdges[i].emplace()); 65 | auto &[dx, dy, dz](directions[i]); 66 | int cx(x), cy(y), cz(z); 67 | bool success{}; 68 | for (edge.nModerators = 0; edge.nModerators <= neutronReach; ++edge.nModerators) { 69 | cx += dx; cy += dy; cz += dz; 70 | if (!tiles.in_bounds(cx, cy, cz)) 71 | break; 72 | bool stop{}; 73 | std::visit(Overload { 74 | [&](Moderator &tile) { 75 | edge.efficiency += moderatorEfficiencies[tile.type]; 76 | edge.flux += moderatorFluxes[tile.type]; 77 | }, 78 | [&](Shield &) { 79 | if (shieldOn) { 80 | stop = true; 81 | } else { 82 | edge.efficiency += shieldEfficiency; 83 | } 84 | }, 85 | [&](Cell &) { 86 | stop = true; 87 | if (edge.nModerators) { 88 | edge.efficiency /= edge.nModerators; 89 | success = true; 90 | } 91 | }, 92 | [&](Irradiator &) { 93 | stop = true; 94 | if (edge.nModerators) { 95 | edge.efficiency = 0.0; 96 | success = true; 97 | } 98 | }, 99 | [&](Reflector &tile) { 100 | stop = true; 101 | if (edge.nModerators && edge.nModerators <= neutronReach / 2) { 102 | edge.efficiency = reflectorEfficiencies[tile.type] * edge.efficiency / edge.nModerators; 103 | edge.flux = static_cast(2 * edge.flux * reflectorFluxMults[tile.type]); 104 | edge.isReflected = true; 105 | success = true; 106 | } 107 | }, 108 | [&](...) { 109 | stop = true; 110 | } 111 | }, tiles(cx, cy, cz)); 112 | if (stop) 113 | break; 114 | } 115 | if (!success) { 116 | cell.fluxEdges[i].reset(); 117 | } else { 118 | totalRawFlux += edge.flux; 119 | } 120 | } 121 | } 122 | 123 | void Evaluation::propagateFlux(int x, int y, int z) { 124 | Cell &cell(*std::get_if(&tiles(x, y, z))); 125 | if (cell.hasAlreadyPropagatedFlux) 126 | return; 127 | cell.hasAlreadyPropagatedFlux = true; 128 | for (int i{}; i < 6; ++i) { 129 | if (!cell.fluxEdges[i].has_value()) 130 | continue; 131 | FluxEdge &edge(*cell.fluxEdges[i]); 132 | if (edge.isReflected) { 133 | cell.flux += edge.flux; 134 | continue; 135 | } 136 | auto &[dx, dy, dz] = directions[i]; 137 | int cx(x + dx * (edge.nModerators + 1)); 138 | int cy(y + dy * (edge.nModerators + 1)); 139 | int cz(z + dz * (edge.nModerators + 1)); 140 | Cell *to(std::get_if(&tiles(cx, cy, cz))); 141 | if (!to) 142 | continue; 143 | to->flux += edge.flux; 144 | if (to->flux >= to->fuel->criticality) 145 | propagateFlux(cx, cy, cz); 146 | } 147 | } 148 | 149 | void Evaluation::propagateFlux() { 150 | bool converged{}; 151 | while (!converged) { 152 | fluxRoots.clear(); 153 | for (auto &[x, y, z] : cells) { 154 | Cell &cell(*std::get_if(&tiles(x, y, z))); 155 | // TODO: Handle neutron source indirection while keeping canonicalization valid. 156 | if (!cell.isExcludedFromFluxRoots && ( 157 | cell.fuel->selfPriming || cell.neutronSource 158 | || shieldOn && cell.flux >= cell.fuel->criticality)) 159 | fluxRoots.emplace_back(x, y, z); 160 | cell.hasAlreadyPropagatedFlux = false; 161 | cell.flux = 0; 162 | } 163 | for (auto &[x, y, z] : fluxRoots) 164 | propagateFlux(x, y, z); 165 | converged = true; 166 | for (auto &[x, y, z] : fluxRoots) { 167 | Cell &cell(*std::get_if(&tiles(x, y, z))); 168 | if (cell.flux < cell.fuel->criticality) { 169 | cell.isExcludedFromFluxRoots = true; 170 | converged = false; 171 | } 172 | } 173 | } 174 | } 175 | 176 | void Evaluation::computeFluxActivation() { 177 | nActiveCells = 0; 178 | maxCellFlux = 0; 179 | for (auto &[x, y, z] : cells) { 180 | Cell &cell(*std::get_if(&tiles(x, y, z))); 181 | maxCellFlux = std::max(maxCellFlux, cell.flux); 182 | cell.isActive = cell.flux >= cell.fuel->criticality; 183 | if (!cell.isActive) 184 | continue; 185 | ++nActiveCells; 186 | for (int i{}; i < 6; ++i) { 187 | if (!cell.fluxEdges[i].has_value()) 188 | continue; 189 | FluxEdge &edge(*cell.fluxEdges[i]); 190 | auto &[dx, dy, dz] = directions[i]; 191 | /* Check if edge ends at inactive cell */ { 192 | int cx(x + dx * (edge.nModerators + 1)); 193 | int cy(y + dy * (edge.nModerators + 1)); 194 | int cz(z + dz * (edge.nModerators + 1)); 195 | Cell *to(std::get_if(&tiles(cx, cy, cz))); 196 | if (to && to->flux < to->fuel->criticality) 197 | continue; 198 | } 199 | ++cell.heatMult; 200 | cell.positionalEfficiency += edge.efficiency; 201 | int cx(x), cy(y), cz(z); 202 | for (int j{}; j <= edge.nModerators; ++j) { 203 | cx += dx; cy += dy; cz += dz; 204 | std::visit(Overload { 205 | [&](Moderator &tile) { 206 | if (!j) 207 | tile.isActive = true; 208 | tile.isFunctional = true; 209 | }, 210 | [&](Shield &tile) { 211 | if (edge.isReflected || i & 1) 212 | tile.flux += edge.flux; 213 | }, 214 | [&](Irradiator &tile) { 215 | tile.flux += edge.flux; 216 | }, 217 | [&](Reflector &tile) { 218 | tile.isActive = true; 219 | }, 220 | [&](...) { } 221 | }, tiles(cx, cy, cz)); 222 | } 223 | } 224 | } 225 | } 226 | 227 | int Evaluation::countAdjacentCells(int x, int y, int z) { 228 | int result{}; 229 | for (auto &[dx, dy, dz] : directions) { 230 | int cx(x + dx), cy(y + dy), cz(z + dz); 231 | if (tiles.in_bounds(cx, cy, cz)) { 232 | Cell *tile(std::get_if(&tiles(cx, cy, cz))); 233 | result += tile && tile->isActive; 234 | } 235 | } 236 | return result; 237 | } 238 | 239 | int Evaluation::countAdjacentCasings(int x, int y, int z) { 240 | int result{}; 241 | for (auto &[dx, dy, dz] : directions) 242 | result += !tiles.in_bounds(x + dx, y + dy, z + dz); 243 | return result; 244 | } 245 | 246 | int Evaluation::countAdjacentReflectors(int x, int y, int z) { 247 | int result{}; 248 | for (auto &[dx, dy, dz] : directions) { 249 | int cx(x + dx), cy(y + dy), cz(z + dz); 250 | if (tiles.in_bounds(cx, cy, cz)) { 251 | Reflector *tile(std::get_if(&tiles(cx, cy, cz))); 252 | result += tile && tile->isActive; 253 | } 254 | } 255 | return result; 256 | } 257 | 258 | int Evaluation::countAdjacentModerators(int x, int y, int z) { 259 | int result{}; 260 | for (auto &[dx, dy, dz] : directions) { 261 | int cx(x + dx), cy(y + dy), cz(z + dz); 262 | if (tiles.in_bounds(cx, cy, cz)) { 263 | Moderator *tile(std::get_if(&tiles(cx, cy, cz))); 264 | result += tile && tile->isActive; 265 | } 266 | } 267 | return result; 268 | } 269 | 270 | int Evaluation::countAdjacentHeatSinks(int type, int x, int y, int z) { 271 | int result{}; 272 | for (auto &[dx, dy, dz] : directions) { 273 | int cx(x + dx), cy(y + dy), cz(z + dz); 274 | if (tiles.in_bounds(cx, cy, cz)) { 275 | HeatSink *tile(std::get_if(&tiles(cx, cy, cz))); 276 | result += tile && tile->isActive && tile->type == type; 277 | } 278 | } 279 | return result; 280 | } 281 | 282 | int Evaluation::countAxialAdjacentHeatSinks(int type, int x, int y, int z) { 283 | int result{}; 284 | for (int i{}; i < 6; ++i) { 285 | bool valid{}; 286 | auto &[dx, dy, dz](directions[i]); 287 | int cx(x + dx), cy(y + dy), cz(z + dz); 288 | if (tiles.in_bounds(cx, cy, cz)) { 289 | HeatSink *tile(std::get_if(&tiles(cx, cy, cz))); 290 | valid = tile && tile->isActive && tile->type == type; 291 | } 292 | if (i & 1) { 293 | result += valid; 294 | } else { 295 | i |= !valid; 296 | } 297 | } 298 | return result; 299 | } 300 | 301 | bool Evaluation::hasAxialAdjacentReflectors(int x, int y, int z) { 302 | for (int i{}; i < 6; ++i) { 303 | bool valid{}; 304 | auto &[dx, dy, dz](directions[i]); 305 | int cx(x + dx), cy(y + dy), cz(z + dz); 306 | if (tiles.in_bounds(cx, cy, cz)) { 307 | Reflector *tile(std::get_if(&tiles(cx, cy, cz))); 308 | valid = tile && tile->isActive; 309 | } 310 | if (i & 1) { 311 | if (valid) { 312 | return true; 313 | } 314 | } else { 315 | i |= !valid; 316 | } 317 | } 318 | return false; 319 | } 320 | 321 | void Evaluation::computeHeatSinkActivation(int x, int y, int z) { 322 | HeatSink &tile(*std::get_if(&tiles(x, y, z))); 323 | switch (tile.type) { 324 | default: // Wt 325 | tile.isActive = countAdjacentCells(x, y, z); 326 | break; 327 | case Tiles::Fe: 328 | tile.isActive = countAdjacentModerators(x, y, z); 329 | break; 330 | case Tiles::Rs: 331 | tile.isActive = countAdjacentCells(x, y, z) && countAdjacentModerators(x, y, z); 332 | break; 333 | case Tiles::Qz: 334 | tile.isActive = countAdjacentHeatSinks(Tiles::Rs, x, y, z); 335 | break; 336 | case Tiles::Ob: 337 | tile.isActive = countAxialAdjacentHeatSinks(Tiles::Gs, x, y, z); 338 | break; 339 | case Tiles::Nr: 340 | tile.isActive = countAdjacentHeatSinks(Tiles::Ob, x, y, z); 341 | break; 342 | case Tiles::Gs: 343 | tile.isActive = countAdjacentModerators(x, y, z) >= 2; 344 | break; 345 | case Tiles::Lp: 346 | tile.isActive = countAdjacentCells(x, y, z) && countAdjacentCasings(x, y, z); 347 | break; 348 | case Tiles::Au: 349 | tile.isActive = countAdjacentHeatSinks(Tiles::Fe, x, y, z) == 2; 350 | break; 351 | case Tiles::Pm: 352 | tile.isActive = countAdjacentHeatSinks(Tiles::Wt, x, y, z) >= 2; 353 | break; 354 | case Tiles::Sm: 355 | tile.isActive = countAdjacentHeatSinks(Tiles::Wt, x, y, z) == 1 && countAdjacentHeatSinks(Tiles::Pb, x, y, z) >= 2; 356 | break; 357 | case Tiles::En: 358 | tile.isActive = countAdjacentReflectors(x, y, z); 359 | break; 360 | case Tiles::Pr: 361 | tile.isActive = countAdjacentHeatSinks(Tiles::Fe, x, y, z) && countAdjacentReflectors(x, y, z); 362 | break; 363 | case Tiles::Dm: 364 | tile.isActive = countAdjacentHeatSinks(Tiles::Au, x, y, z) && countAdjacentCells(x, y, z); 365 | break; 366 | case Tiles::Em: 367 | tile.isActive = countAdjacentHeatSinks(Tiles::Pm, x, y, z) && countAdjacentModerators(x, y, z); 368 | break; 369 | case Tiles::Cu: 370 | tile.isActive = countAdjacentHeatSinks(Tiles::Wt, x, y, z); 371 | break; 372 | case Tiles::Sn: 373 | tile.isActive = countAxialAdjacentHeatSinks(Tiles::Lp, x, y, z); 374 | break; 375 | case Tiles::Pb: 376 | tile.isActive = countAdjacentHeatSinks(Tiles::Fe, x, y, z); 377 | break; 378 | case Tiles::B: 379 | tile.isActive = countAdjacentHeatSinks(Tiles::Qz, x, y, z) == 1 && countAdjacentCasings(x, y, z); 380 | break; 381 | case Tiles::Li: 382 | tile.isActive = countAxialAdjacentHeatSinks(Tiles::Pb, x, y, z) == 1 && countAdjacentCasings(x, y, z); 383 | break; 384 | case Tiles::Mg: 385 | tile.isActive = countAdjacentModerators(x, y, z) == 1 && countAdjacentCasings(x, y, z); 386 | break; 387 | case Tiles::Mn: 388 | tile.isActive = countAdjacentCells(x, y, z) >= 2; 389 | break; 390 | case Tiles::Al: 391 | tile.isActive = countAdjacentHeatSinks(Tiles::Qz, x, y, z) && countAdjacentHeatSinks(Tiles::Lp, x, y, z); 392 | break; 393 | case Tiles::Ag: 394 | tile.isActive = countAdjacentHeatSinks(Tiles::Gs, x, y, z) >= 2 && countAdjacentHeatSinks(Tiles::Sn, x, y, z); 395 | break; 396 | case Tiles::Fl: 397 | tile.isActive = countAdjacentHeatSinks(Tiles::Au, x, y, z) && countAdjacentHeatSinks(Tiles::Pm, x, y, z); 398 | break; 399 | case Tiles::Vi: 400 | tile.isActive = countAdjacentHeatSinks(Tiles::En, x, y, z) && countAdjacentHeatSinks(Tiles::Rs, x, y, z); 401 | break; 402 | case Tiles::Cb: 403 | tile.isActive = countAdjacentHeatSinks(Tiles::Cu, x, y, z) && countAdjacentHeatSinks(Tiles::En, x, y, z); 404 | break; 405 | case Tiles::As: 406 | tile.isActive = hasAxialAdjacentReflectors(x, y, z); 407 | break; 408 | case Tiles::N: 409 | tile.isActive = countAdjacentHeatSinks(Tiles::Cu, x, y, z) >= 2 && countAdjacentHeatSinks(Tiles::Pr, x, y, z); 410 | break; 411 | case Tiles::He: 412 | tile.isActive = countAdjacentHeatSinks(Tiles::Rs, x, y, z) == 2; 413 | break; 414 | case Tiles::Ed: 415 | tile.isActive = countAdjacentModerators(x, y, z) >= 3; 416 | break; 417 | case Tiles::Cr: 418 | tile.isActive = countAdjacentCells(x, y, z) >= 3; 419 | } 420 | } 421 | 422 | bool Evaluation::propagateCluster(int id, int x, int y, int z) { 423 | if (!tiles.in_bounds(x, y, z)) 424 | return true; 425 | bool valid{}; 426 | Tile &tile(tiles(x, y, z)); 427 | std::visit(Overload { 428 | [&](Cell &tile) { valid = tile.isActive && tile.cluster < 0; }, 429 | [&](Shield &tile) { valid = !shieldOn && tile.flux && tile.cluster < 0; }, 430 | [&](HeatSink &tile) { valid = tile.isActive && tile.cluster < 0; }, 431 | [&](Irradiator &tile) { valid = tile.flux && tile.cluster < 0; }, 432 | [&](Conductor &tile) { valid = tile.cluster < 0; }, 433 | [](...) {} 434 | }, tile); 435 | if (!valid) 436 | return false; 437 | if (id < 0) { 438 | id = clusters.size(); 439 | clusters.emplace_back(); 440 | } 441 | std::visit(Overload { 442 | [&](Cell &tile) { tile.cluster = id; }, 443 | [&](Shield &tile) { tile.cluster = id; }, 444 | [&](HeatSink &tile) { tile.cluster = id; }, 445 | [&](Irradiator &tile) { tile.cluster = id; }, 446 | [&](Conductor &tile) { tile.cluster = id; }, 447 | [](...) { throw; } 448 | }, tile); 449 | Cluster &cluster(clusters[id]); 450 | cluster.tiles.emplace_back(x, y, z); 451 | for (auto &[dx, dy, dz] : directions) 452 | if (propagateCluster(id, x + dx, y + dy, z + dz)) 453 | cluster.hasCasingConnection = true; 454 | return false; 455 | } 456 | 457 | void Evaluation::computeClusterStats(Cluster &cluster) { 458 | for (auto &[x, y, z] : cluster.tiles) { 459 | std::visit(Overload { 460 | [&](HeatSink &tile) { 461 | cluster.cooling += coolingRates[tile.type]; 462 | }, 463 | [&](Cell &tile) { 464 | tile.fluxEfficiency = 1 / (1 + std::exp(2 * (tile.flux - 2 * tile.fuel->criticality))); 465 | tile.efficiency = tile.positionalEfficiency * tile.fuel->efficiency * tile.fluxEfficiency; 466 | if (tile.neutronSource) 467 | tile.efficiency *= sourceEfficiencies[tile.neutronSource - 1]; 468 | cluster.rawEfficiency += tile.efficiency; 469 | cluster.rawOutput += tile.efficiency * tile.fuel->heat; 470 | cluster.heat += tile.heatMult * tile.fuel->heat; 471 | }, 472 | [&](Shield &tile) { 473 | cluster.heat += tile.flux * shieldHeatPerFlux; 474 | }, 475 | // Note: Irradiators are ignored as they're all currently zero heats. 476 | [](...) {} 477 | }, tiles(x, y, z)); 478 | } 479 | cluster.netHeat = cluster.heat - cluster.cooling; 480 | cluster.coolingPenaltyMult = std::min(1.0, static_cast(cluster.heat + coolingEfficiencyLeniency) / cluster.cooling); 481 | cluster.output = cluster.rawOutput * cluster.coolingPenaltyMult; 482 | cluster.efficiency = cluster.rawEfficiency * cluster.coolingPenaltyMult; 483 | } 484 | 485 | void Evaluation::computeSparsity() { 486 | nFunctionalBlocks = 0; 487 | for (int x{}; x < settings->sizeX; ++x) { 488 | for (int y{}; y < settings->sizeY; ++y) { 489 | for (int z{}; z < settings->sizeZ; ++z) { 490 | std::visit(Overload { 491 | [&](Cell &tile) { nFunctionalBlocks += tile.isActive; }, 492 | [&](Moderator &tile) { nFunctionalBlocks += tile.isFunctional; }, 493 | [&](Reflector &tile) { nFunctionalBlocks += tile.isActive; }, 494 | [&](Shield &tile) { nFunctionalBlocks += !!tile.flux; }, 495 | [&](Irradiator &tile) { nFunctionalBlocks += !!tile.flux; }, 496 | [&](HeatSink &tile) { nFunctionalBlocks += tile.isActive; }, 497 | [](...) {} 498 | }, tiles(x, y, z)); 499 | } 500 | } 501 | } 502 | density = static_cast(nFunctionalBlocks) / (settings->sizeX * settings->sizeY * settings->sizeZ); 503 | if (density >= sparsityPenaltyThreshold) 504 | sparsityPenalty = 1.0; 505 | else 506 | sparsityPenalty = maxSparsityPenaltyMult + (1 - maxSparsityPenaltyMult) 507 | * std::sin(density * std::acos(-1.0) / (2 * sparsityPenaltyThreshold)); 508 | } 509 | 510 | void Evaluation::computeStats() { 511 | totalPositiveNetHeat = 0; 512 | rawEfficiency = 0.0; 513 | rawOutput = 0.0; 514 | for (Cluster &cluster : clusters) { 515 | if (cluster.hasCasingConnection) { 516 | totalPositiveNetHeat += std::max(0, cluster.netHeat); 517 | rawEfficiency += cluster.efficiency; 518 | rawOutput += cluster.output; 519 | } else { 520 | totalPositiveNetHeat += cluster.heat; 521 | } 522 | } 523 | if (nActiveCells) 524 | rawEfficiency /= nActiveCells; 525 | efficiency = rawEfficiency * sparsityPenalty; 526 | output = rawOutput * sparsityPenalty; 527 | irradiatorFlux = 0; 528 | for (auto &[x, y, z] : irradiators) { 529 | Irradiator &tile(*std::get_if(&tiles(x, y, z))); 530 | irradiatorFlux += tile.flux; 531 | } 532 | } 533 | 534 | void Evaluation::run(const State &state) { 535 | cells.clear(); 536 | tier1s.clear(); 537 | tier2s.clear(); 538 | tier3s.clear(); 539 | shields.clear(); 540 | irradiators.clear(); 541 | conductors.clear(); 542 | for (int x{}; x < settings->sizeX; ++x) { 543 | for (int y{}; y < settings->sizeY; ++y) { 544 | for (int z{}; z < settings->sizeZ; ++z) { 545 | Tile &tile(tiles(x, y, z)); 546 | int type(state(x, y, z)); 547 | if (type < Tiles::M0) { 548 | tile.emplace(type); 549 | switch (type) { 550 | case Tiles::Wt: 551 | case Tiles::Fe: 552 | case Tiles::Rs: 553 | case Tiles::Gs: 554 | case Tiles::Lp: 555 | case Tiles::En: 556 | case Tiles::Mg: 557 | case Tiles::Mn: 558 | case Tiles::As: 559 | case Tiles::Ed: 560 | case Tiles::Cr: 561 | tier1s.emplace_back(x, y, z); 562 | break; 563 | case Tiles::Qz: 564 | case Tiles::Ob: 565 | case Tiles::Au: 566 | case Tiles::Pm: 567 | case Tiles::Pr: 568 | case Tiles::Cu: 569 | case Tiles::Sn: 570 | case Tiles::Pb: 571 | case Tiles::Vi: 572 | case Tiles::He: 573 | tier2s.emplace_back(x, y, z); 574 | break; 575 | default: 576 | tier3s.emplace_back(x, y, z); 577 | } 578 | } else if (type < Tiles::R0) { 579 | tile.emplace(type - Tiles::M0); 580 | } else if (type < Tiles::Shield) { 581 | tile.emplace(type - Tiles::R0); 582 | } else switch (type) { 583 | case Tiles::Shield: 584 | tile.emplace(); 585 | shields.emplace_back(x, y, z); 586 | break; 587 | case Tiles::Irradiator: 588 | tile.emplace(); 589 | irradiators.emplace_back(x, y, z); 590 | break; 591 | case Tiles::Conductor: 592 | tile.emplace(); 593 | conductors.emplace_back(x, y, z); 594 | break; 595 | case Tiles::Air: 596 | tile.emplace(); 597 | break; 598 | default: 599 | auto &cellType(settings->cellTypes[type - Tiles::C0]); 600 | tile.emplace(&settings->fuels[cellType.first], cellType.second); 601 | cells.emplace_back(x, y, z); 602 | } 603 | } 604 | } 605 | } 606 | totalRawFlux = 0; 607 | for (auto &[x, y, z] : cells) { 608 | checkNeutronSource(x, y, z); 609 | computeFluxEdge(x, y, z); 610 | } 611 | propagateFlux(); 612 | computeFluxActivation(); 613 | for (auto &[x, y, z] : tier1s) 614 | computeHeatSinkActivation(x, y, z); 615 | for (auto &[x, y, z] : tier2s) 616 | computeHeatSinkActivation(x, y, z); 617 | for (auto &[x, y, z] : tier3s) 618 | computeHeatSinkActivation(x, y, z); 619 | clusters.clear(); 620 | for (auto &[x, y, z] : cells) 621 | propagateCluster(-1, x, y, z); 622 | if (!shieldOn) 623 | for (auto &[x, y, z] : shields) 624 | propagateCluster(-1, x, y, z); 625 | for (auto &[x, y, z] : irradiators) 626 | propagateCluster(-1, x, y, z); 627 | for (auto &i : clusters) 628 | computeClusterStats(i); 629 | computeSparsity(); 630 | computeStats(); 631 | } 632 | 633 | void Evaluation::removeInactiveHeatSink(State &state, int x, int y, int z) { 634 | HeatSink &tile(*std::get_if(&tiles(x, y, z))); 635 | if (!tile.isActive) 636 | state(x, y, z) = Tiles::Air; 637 | } 638 | 639 | void Evaluation::canonicalize(State &state) { 640 | for (int x{}; x < settings->sizeX; ++x) { 641 | for (int y{}; y < settings->sizeY; ++y) { 642 | for (int z{}; z < settings->sizeZ; ++z) { 643 | std::visit(Overload { 644 | [&](Cell &tile) { 645 | if (!tile.isActive) 646 | state(x, y, z) = Tiles::Air; 647 | else if (tile.isNeutronSourceBlocked) 648 | state(x, y, z) -= settings->cellTypes[state(x, y, z) - Tiles::C0].second; 649 | }, 650 | [&](HeatSink &tile) { 651 | // Note: according to planner, heat sinks without cluster still count as functional blocks. 652 | if (!tile.isActive) 653 | state(x, y, z) = Tiles::Air; 654 | }, 655 | [&](Irradiator &tile) { 656 | if (!tile.flux) 657 | state(x, y, z) = Tiles::Air; 658 | }, 659 | [&](Moderator &tile) { 660 | if (!tile.isFunctional) 661 | state(x, y, z) = Tiles::Air; 662 | }, 663 | [&](Shield &tile) { 664 | if (tile.cluster < 0) 665 | state(x, y, z) = Tiles::Air; 666 | }, 667 | [&](Reflector &tile) { 668 | if (!tile.isActive) 669 | state(x, y, z) = Tiles::Air; 670 | }, 671 | [&](Conductor &tile) { 672 | // TODO: remove redundant conductors. 673 | if (tile.cluster < 0) 674 | state(x, y, z) = Tiles::Air; 675 | }, 676 | [&](Air &) {} 677 | }, tiles(x, y, z)); 678 | } 679 | } 680 | } 681 | } 682 | } 683 | -------------------------------------------------------------------------------- /OverhaulFission.h: -------------------------------------------------------------------------------- 1 | #ifndef _OVERHAUL_FISSION_H_ 2 | #define _OVERHAUL_FISSION_H_ 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace OverhaulFission { 9 | constexpr double moderatorEfficiencies[] { 1.1, 1.05, 1.0 }; 10 | constexpr double sourceEfficiencies[] { 1.0, 0.95, 0.9 }; 11 | constexpr double reflectorEfficiencies[] { 0.5, 0.25 }; 12 | constexpr double reflectorFluxMults[] { 1.0, 0.5 }; 13 | constexpr double sparsityPenaltyThreshold(0.75); 14 | constexpr int moderatorFluxes[] { 10, 22, 36 }; 15 | constexpr double maxSparsityPenaltyMult(0.5); 16 | constexpr int coolingEfficiencyLeniency(10); 17 | constexpr double shieldEfficiency(0.5); 18 | constexpr int shieldHeatPerFlux(5); 19 | constexpr int neutronReach(4); 20 | constexpr int coolingRates[] { 21 | 55, 50, 85, 80, 70, 105, 90, 100, 110, 115, 145, 65, 95, 200, 195, 75, 120, 22 | 60, 160, 130, 125, 150, 175, 170, 165, 180, 140, 135, 185, 190, 155, 205 23 | }; 24 | 25 | namespace Tiles { enum { 26 | // Heat sink 27 | Wt, Fe, Rs, Qz, Ob, Nr, Gs, Lp, Au, Pm, Sm, En, Pr, Dm, Em, Cu, 28 | Sn, Pb, B, Li, Mg, Mn, Al, Ag, Fl, Vi, Cb, As, N, He, Ed, Cr, 29 | // Moderator 30 | M0, M1, M2, 31 | // Reflector 32 | R0, R1, 33 | // Other 34 | Shield, Irradiator, Conductor, Air, C0 35 | }; } 36 | 37 | enum { 38 | GoalOutput, 39 | GoalFuelUse, 40 | GoalEfficiency, 41 | GoalIrradiation 42 | }; 43 | 44 | struct Fuel { 45 | double efficiency; 46 | int limit; 47 | int criticality; 48 | int heat; 49 | bool selfPriming; 50 | }; 51 | 52 | struct Settings { 53 | int sizeX, sizeY, sizeZ; 54 | std::vector fuels; 55 | int limits[Tiles::Air]; 56 | int sourceLimits[3]; 57 | int goal; 58 | bool controllable; 59 | bool symX, symY, symZ; 60 | // Computed 61 | std::vector> cellTypes; 62 | double maxOutput; 63 | int minCriticality; 64 | int minHeat; 65 | 66 | void compute(); 67 | }; 68 | 69 | using State = xt::xtensor; 70 | using Coord = std::tuple; 71 | extern const Coord directions[6]; 72 | 73 | struct FluxEdge { 74 | double efficiency{}; 75 | int flux{}, nModerators; 76 | bool isReflected{}; 77 | }; 78 | 79 | struct Air {}; 80 | 81 | struct Cell { 82 | const Fuel *fuel; 83 | std::optional fluxEdges[6]; 84 | double positionalEfficiency{}, fluxEfficiency, efficiency; 85 | int neutronSource, flux{}, heatMult{}, cluster{-1}; 86 | bool isNeutronSourceBlocked{}; 87 | bool isExcludedFromFluxRoots{}; 88 | bool hasAlreadyPropagatedFlux; 89 | bool isActive; 90 | 91 | Cell(const Fuel *fuel, int neutronSource) 92 | :fuel(fuel), neutronSource(neutronSource) {} 93 | }; 94 | 95 | struct Moderator { 96 | int type; 97 | bool isActive{}; 98 | bool isFunctional{}; 99 | 100 | Moderator(int type) 101 | :type(type) {} 102 | }; 103 | 104 | struct Reflector { 105 | int type; 106 | bool isActive{}; 107 | 108 | Reflector(int type) :type(type) {} 109 | }; 110 | 111 | struct Shield { 112 | int flux{}, cluster{-1}; 113 | }; 114 | 115 | struct Irradiator { 116 | int flux{}, cluster{-1}; 117 | }; 118 | 119 | struct Conductor { 120 | int cluster{-1}; 121 | }; 122 | 123 | struct HeatSink { 124 | int type, cluster{-1}; 125 | bool isActive; 126 | 127 | HeatSink(int type) 128 | :type(type) {} 129 | }; 130 | 131 | using Tile = std::variant; 132 | template struct Overload : T... { using T::operator()...; }; 133 | template Overload(T...) -> Overload; 134 | 135 | struct Cluster { 136 | std::vector tiles; 137 | double rawOutput{}, coolingPenaltyMult, output, rawEfficiency{}, efficiency; 138 | // Note: not having fuelDurationMult as the generator doesn't generate heat-positive reactor. 139 | int heat{}, cooling{}, netHeat; 140 | bool hasCasingConnection{}; 141 | }; 142 | 143 | struct Evaluation { 144 | xt::xtensor tiles; 145 | std::vector cells, tier1s, tier2s, tier3s, shields, irradiators, conductors, fluxRoots; 146 | std::vector clusters; 147 | const Settings *settings; 148 | double rawEfficiency, efficiency, rawOutput, output, density, sparsityPenalty; 149 | int nFunctionalBlocks, totalPositiveNetHeat, irradiatorFlux, nActiveCells, totalRawFlux, maxCellFlux; 150 | bool shieldOn; 151 | private: 152 | void checkNeutronSource(int x, int y, int z); 153 | void computeFluxEdge(int x, int y, int z); 154 | void propagateFlux(int x, int y, int z); 155 | void propagateFlux(); 156 | void computeFluxActivation(); 157 | int countAdjacentCells(int x, int y, int z); 158 | int countAdjacentCasings(int x, int y, int z); 159 | int countAdjacentReflectors(int x, int y, int z); 160 | int countAdjacentModerators(int x, int y, int z); 161 | int countAdjacentHeatSinks(int type, int x, int y, int z); 162 | int countAxialAdjacentHeatSinks(int type, int x, int y, int z); 163 | bool hasAxialAdjacentReflectors(int x, int y, int z); 164 | void computeHeatSinkActivation(int x, int y, int z); 165 | bool propagateCluster(int id, int x, int y, int z); 166 | void computeClusterStats(Cluster &cluster); 167 | void computeSparsity(); 168 | void computeStats(); 169 | void removeInactiveHeatSink(State &state, int x, int y, int z); 170 | public: 171 | void initialize(const Settings &settings, bool shieldOn); 172 | void run(const State &state); 173 | void canonicalize(State &state); 174 | }; 175 | } 176 | 177 | #endif 178 | -------------------------------------------------------------------------------- /OverhaulFissionNet.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "OverhaulFissionNet.h" 3 | 4 | namespace OverhaulFission { 5 | Net::Net(Opt &opt) :opt(opt), mCorrector(1), rCorrector(1), trajectoryLength(), writePos() { 6 | for (int i{}; i < Tiles::Air; ++i) 7 | if (opt.settings.limits[i]) 8 | tileMap.emplace(i, tileMap.size()); 9 | tileMap.emplace(Tiles::Air, tileMap.size()); 10 | nFeatures = static_cast(tileMap.size()) * 2 - 1 + nStatisticalFeatures; 11 | batchInput = xt::empty({nMiniBatch, nFeatures}); 12 | batchTarget = xt::empty({nMiniBatch}); 13 | 14 | wLayer1 = xt::random::randn({nLayer1, nFeatures}, 0.0, 1.0 / std::sqrt(nFeatures), opt.rng); 15 | mwLayer1 = xt::zeros_like(wLayer1); 16 | rwLayer1 = xt::zeros_like(wLayer1); 17 | bLayer1 = xt::zeros({nLayer1}); 18 | mbLayer1 = xt::zeros_like(bLayer1); 19 | rbLayer1 = xt::zeros_like(bLayer1); 20 | 21 | wLayer2 = xt::random::randn({nLayer2, nLayer1}, 0.0, 1.0 / std::sqrt(nLayer1), opt.rng); 22 | mwLayer2 = xt::zeros_like(wLayer2); 23 | rwLayer2 = xt::zeros_like(wLayer2); 24 | bLayer2 = xt::zeros({nLayer2}); 25 | mbLayer2 = xt::zeros_like(bLayer2); 26 | rbLayer2 = xt::zeros_like(bLayer2); 27 | 28 | wOutput = xt::random::randn({nLayer2}, 0.0, 1.0 / std::sqrt(nLayer2), opt.rng); 29 | mwOutput = xt::zeros_like(wOutput); 30 | rwOutput = xt::zeros_like(wOutput); 31 | bOutput = 0.0; 32 | mbOutput = 0.0; 33 | rbOutput = 0.0; 34 | } 35 | 36 | void Net::appendTrajectory(xt::xtensor features) { 37 | if (trajectoryLength < nPool) 38 | ++trajectoryLength; 39 | if (pool.size() == nPool) 40 | pool[writePos].first = std::move(features); 41 | else 42 | pool.emplace_back(std::move(features), 0.0); 43 | if (++writePos == nPool) 44 | writePos = 0; 45 | } 46 | 47 | void Net::finishTrajectory(double target) { 48 | int pos(writePos); 49 | for (int i{}; i < trajectoryLength; ++i) { 50 | if (--pos < 0) 51 | pos = nPool - 1; 52 | pool[pos].second = target; 53 | } 54 | std::cout << "trajectoryLength: " << trajectoryLength << std::endl; 55 | std::cout << "pool: " << pool.size() << std::endl; 56 | } 57 | 58 | xt::xtensor Net::extractFeatures(const Sample &sample) { 59 | xt::xtensor vInput(xt::zeros({nFeatures})); 60 | for (int x{}; x < opt.settings.sizeX; ++x) { 61 | for (int y{}; y < opt.settings.sizeY; ++y) { 62 | for (int z{}; z < opt.settings.sizeZ; ++z) { 63 | int tile(sample.state(x, y, z)); 64 | if (tile > Tiles::Air) 65 | continue; 66 | int index(tileMap[tile]); 67 | ++vInput[index]; 68 | if (tile == Tiles::Air) 69 | continue; 70 | bool isFunctional; 71 | std::visit(Overload { 72 | [&](const Moderator &tile) { isFunctional = tile.isFunctional; }, 73 | [&](const Reflector &tile) { isFunctional = tile.isActive; }, 74 | [&](const Shield &tile) { isFunctional = tile.flux; }, 75 | [&](const Irradiator &tile) { isFunctional = tile.flux; }, 76 | [&](const HeatSink &tile) { isFunctional = tile.isActive; }, 77 | [&](const Conductor &tile) { isFunctional = tile.cluster >= 0; }, 78 | [] (...) { throw; } 79 | }, sample.value.tiles(x, y, z)); 80 | vInput[tileMap.size() + index] += isFunctional; 81 | } 82 | } 83 | } 84 | vInput.periodic(-8) = sample.value.cells.size(); 85 | vInput.periodic(-7) = sample.value.nActiveCells; 86 | vInput.periodic(-6) = sample.value.clusters.size(); 87 | vInput /= opt.settings.sizeX * opt.settings.sizeY * opt.settings.sizeZ; 88 | vInput.periodic(-5) = static_cast(sample.value.totalRawFlux) / opt.settings.minCriticality; 89 | vInput.periodic(-4) = static_cast(sample.value.totalPositiveNetHeat) / opt.settings.minHeat; 90 | vInput.periodic(-3) = sample.value.output / opt.settings.maxOutput; 91 | vInput.periodic(-2) = sample.value.efficiency; 92 | vInput.periodic(-1) = static_cast(sample.value.irradiatorFlux) / opt.settings.minCriticality; 93 | return vInput; 94 | } 95 | 96 | double Net::infer(const Sample &sample) { 97 | auto vInput(extractFeatures(sample)); 98 | xt::xtensor vLayer1(bLayer1 + xt::sum(wLayer1 * vInput, -1)); 99 | xt::xtensor vPwlLayer1(vLayer1 * leak + xt::clip(vLayer1, -1.0, 1.0)); 100 | xt::xtensor vLayer2(bLayer2 + xt::sum(wLayer2 * vPwlLayer1, -1)); 101 | xt::xtensor vPwlLayer2(vLayer2 * leak + xt::clip(vLayer2, -1.0, 1.0)); 102 | return bOutput + xt::sum(wOutput * vPwlLayer2)(); 103 | } 104 | 105 | double Net::train() { 106 | // Assemble batch 107 | std::uniform_int_distribution dist(0, pool.size() - 1); 108 | for (int i{}; i < nMiniBatch; ++i) { 109 | auto &sample(pool[dist(opt.rng)]); 110 | xt::view(batchInput, i, xt::all()) = sample.first; 111 | batchTarget(i) = sample.second; 112 | } 113 | 114 | // Forward 115 | xt::xtensor vLayer1(bLayer1 + xt::sum(wLayer1 * xt::view(batchInput, xt::all(), xt::newaxis(), xt::all()), -1)); 116 | xt::xtensor vPwlLayer1(vLayer1 * leak + xt::clip(vLayer1, -1.0, 1.0)); 117 | xt::xtensor vLayer2(bLayer2 + xt::sum(wLayer2 * xt::view(vPwlLayer1, xt::all(), xt::newaxis(), xt::all()), -1)); 118 | xt::xtensor vPwlLayer2(vLayer2 * leak + xt::clip(vLayer2, -1.0, 1.0)); 119 | xt::xtensor vOutput(bOutput + xt::sum(wOutput * vPwlLayer2, -1)); 120 | xt::xtensor losses(xt::square(vOutput - batchTarget)); 121 | double loss(xt::mean(losses)()); 122 | 123 | // Backward 124 | xt::xtensor gvOutput((vOutput - batchTarget) * 2 / nMiniBatch); 125 | double gbOutput(xt::sum(gvOutput)()); 126 | xt::xtensor gwOutput(xt::sum(xt::view(gvOutput, xt::all(), xt::newaxis()) * vPwlLayer2, 0)); 127 | xt::xtensor gvPwlLayer2(xt::empty_like(vPwlLayer2)); 128 | for (int i{}; i < nMiniBatch; ++i) 129 | for (int j{}; j < nLayer2; ++j) 130 | gvPwlLayer2(i, j) = gvOutput(i) * wOutput(j); 131 | xt::xtensor gvLayer2(gvPwlLayer2 * (leak + (xt::abs(vLayer2) < 1.0))); 132 | xt::xtensor gbLayer2(xt::sum(gvLayer2, 0)); 133 | xt::xtensor gwLayer2(xt::empty_like(wLayer2)); 134 | for (int i{}; i < nLayer2; ++i) 135 | for (int j{}; j < nLayer1; ++j) 136 | gwLayer2(i, j) = xt::sum(xt::view(gvLayer2, xt::all(), i) * xt::view(vPwlLayer1, xt::all(), j))(); 137 | xt::xtensor gvPwlLayer1(xt::sum(xt::view(gvLayer2, xt::all(), xt::all(), xt::newaxis()) * wLayer2, -2)); 138 | xt::xtensor gvLayer1(gvPwlLayer1 * (leak + (xt::abs(vLayer1) < 1.0))); 139 | xt::xtensor gbLayer1(xt::sum(gvLayer1, 0)); 140 | xt::xtensor gwLayer1(xt::empty_like(wLayer1)); 141 | for (int i{}; i < nLayer1; ++i) 142 | for (int j{}; j < nFeatures; ++j) 143 | gwLayer1(i, j) = xt::sum(xt::view(gvLayer1, xt::all(), i) * xt::view(batchInput, xt::all(), j))(); 144 | 145 | // Adam 146 | mCorrector *= mRate; 147 | mwLayer1 = mRate * mwLayer1 + (1 - mRate) * gwLayer1; 148 | mbLayer1 = mRate * mbLayer1 + (1 - mRate) * gbLayer1; 149 | mwLayer2 = mRate * mwLayer2 + (1 - mRate) * gwLayer2; 150 | mbLayer2 = mRate * mbLayer2 + (1 - mRate) * gbLayer2; 151 | mwOutput = mRate * mwOutput + (1 - mRate) * gwOutput; 152 | mbOutput = mRate * mbOutput + (1 - mRate) * gbOutput; 153 | 154 | rCorrector *= rRate; 155 | rwLayer1 = rRate * rwLayer1 + (1 - rRate) * xt::square(gwLayer1); 156 | rbLayer1 = rRate * rbLayer1 + (1 - rRate) * xt::square(gbLayer1); 157 | rwLayer2 = rRate * rwLayer2 + (1 - rRate) * xt::square(gwLayer2); 158 | rbLayer2 = rRate * rbLayer2 + (1 - rRate) * xt::square(gbLayer2); 159 | rwOutput = rRate * rwOutput + (1 - rRate) * xt::square(gwOutput); 160 | rbOutput = rRate * rbOutput + (1 - rRate) * (gbOutput * gbOutput); 161 | 162 | wLayer1 -= lRate * mwLayer1 / ((1 - mCorrector) * (xt::sqrt(rwLayer1 / (1 - rCorrector)) + 1e-8)); 163 | bLayer1 -= lRate * mbLayer1 / ((1 - mCorrector) * (xt::sqrt(rbLayer1 / (1 - rCorrector)) + 1e-8)); 164 | wLayer2 -= lRate * mwLayer2 / ((1 - mCorrector) * (xt::sqrt(rwLayer2 / (1 - rCorrector)) + 1e-8)); 165 | bLayer2 -= lRate * mbLayer2 / ((1 - mCorrector) * (xt::sqrt(rbLayer2 / (1 - rCorrector)) + 1e-8)); 166 | wOutput -= lRate * mwOutput / ((1 - mCorrector) * (xt::sqrt(rwOutput / (1 - rCorrector)) + 1e-8)); 167 | bOutput -= lRate * mbOutput / ((1 - mCorrector) * (std::sqrt(rbOutput / (1 - rCorrector)) + 1e-8)); 168 | 169 | return loss; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /OverhaulFissionNet.h: -------------------------------------------------------------------------------- 1 | #ifndef _OVERHAUL_FISSION_NET_H_ 2 | #define _OVERHAUL_FISSION_NET_H_ 3 | #include 4 | #include "OptOverhaulFission.h" 5 | 6 | namespace OverhaulFission { 7 | constexpr int nStatisticalFeatures(8), nLayer1(128), nLayer2(64), nMiniBatch(64), nEpoch(2), nPool(10'000'000); 8 | constexpr double lRate(0.001), mRate(0.9), rRate(0.999), leak(0.1); 9 | 10 | class Net { 11 | Opt &opt; 12 | double mCorrector, rCorrector; 13 | std::unordered_map tileMap; 14 | int nFeatures; 15 | 16 | // Data Pool 17 | xt::xtensor batchInput; 18 | xt::xtensor batchTarget; 19 | std::vector, double>> pool; 20 | int trajectoryLength, writePos; 21 | 22 | xt::xtensor wLayer1, mwLayer1, rwLayer1; 23 | xt::xtensor bLayer1, mbLayer1, rbLayer1; 24 | xt::xtensor wLayer2, mwLayer2, rwLayer2; 25 | xt::xtensor bLayer2, mbLayer2, rbLayer2; 26 | xt::xtensor wOutput, mwOutput, rwOutput; 27 | double bOutput, mbOutput, rbOutput; 28 | 29 | public: 30 | Net(Opt &opt); 31 | xt::xtensor extractFeatures(const Sample &sample); 32 | double infer(const Sample &sample); 33 | void newTrajectory() { trajectoryLength = 0; } 34 | void appendTrajectory(xt::xtensor features); 35 | void finishTrajectory(double target); 36 | int getTrajectoryLength() const { return trajectoryLength; } 37 | double train(); 38 | }; 39 | } 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Web version: https://leu-235.com/ (Built using emscripten. See [web/compile.bat](web/compile.bat) for details.) 2 | 3 | To build the benchmark, clone [xtl](https://github.com/xtensor-stack/xtl) and [xtensor](https://github.com/xtensor-stack/xtensor) to the parent directory of this repo and run CMake. 4 | -------------------------------------------------------------------------------- /web/Bindings.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../FissionNet.h" 3 | #include "../OverhaulFissionNet.h" 4 | 5 | static void setLimit(Fission::Settings &x, int index, int limit) { 6 | x.limit[index] = limit; 7 | } 8 | 9 | static void setRate(Fission::Settings &x, int index, double rate) { 10 | x.coolingRates[index] = rate; 11 | } 12 | 13 | static emscripten::val getData(const Fission::Sample &x) { 14 | return emscripten::val(emscripten::typed_memory_view(x.state.size(), x.state.data())); 15 | } 16 | 17 | static int getShape(const Fission::Sample &x, int i) { 18 | return x.state.shape(i); 19 | } 20 | 21 | static int getStride(const Fission::Sample &x, int i) { 22 | return x.state.strides()[i]; 23 | } 24 | 25 | static double getPower(const Fission::Sample &x) { 26 | return x.value.power; 27 | } 28 | 29 | static double getHeat(const Fission::Sample &x) { 30 | return x.value.heat; 31 | } 32 | 33 | static double getCooling(const Fission::Sample &x) { 34 | return x.value.cooling; 35 | } 36 | 37 | static double getNetHeat(const Fission::Sample &x) { 38 | return x.value.netHeat; 39 | } 40 | 41 | static double getDutyCycle(const Fission::Sample &x) { 42 | return x.value.dutyCycle; 43 | } 44 | 45 | static double getAvgPower(const Fission::Sample &x) { 46 | return x.value.avgPower; 47 | } 48 | 49 | static double getAvgBreed(const Fission::Sample &x) { 50 | return x.value.avgBreed; 51 | } 52 | 53 | static double getEfficiency(const Fission::Sample &x) { 54 | return x.value.efficiency; 55 | } 56 | 57 | static emscripten::val getLossHistory(const Fission::Opt &opt) { 58 | auto &data(opt.getLossHistory()); 59 | return emscripten::val(emscripten::typed_memory_view(data.size(), data.data())); 60 | } 61 | 62 | static void clearFuels(OverhaulFission::Settings &settings) { 63 | settings.fuels.clear(); 64 | } 65 | 66 | static void addFuel(OverhaulFission::Settings &settings, double efficiency, int limit, int criticality, int heat, bool selfPriming) { 67 | auto &fuel(settings.fuels.emplace_back()); 68 | fuel.efficiency = efficiency; 69 | fuel.limit = limit; 70 | fuel.criticality = criticality; 71 | fuel.heat = heat; 72 | fuel.selfPriming = selfPriming; 73 | } 74 | 75 | static void overhaulSetLimit(OverhaulFission::Settings &x, int index, int limit) { 76 | x.limits[index] = limit; 77 | } 78 | 79 | static void setSourceLimit(OverhaulFission::Settings &x, int index, int limit) { 80 | x.sourceLimits[index] = limit; 81 | } 82 | 83 | static emscripten::val overhaulGetData(const OverhaulFission::Sample &x) { 84 | return emscripten::val(emscripten::typed_memory_view(x.state.size(), x.state.data())); 85 | } 86 | 87 | static int overhaulGetShape(const OverhaulFission::Sample &x, int i) { 88 | return x.state.shape(i); 89 | } 90 | 91 | static int overhaulGetStride(const OverhaulFission::Sample &x, int i) { 92 | return x.state.strides()[i]; 93 | } 94 | 95 | static double getOutput(const OverhaulFission::Sample &x) { 96 | return x.value.output; 97 | } 98 | 99 | static int getFuelUse(const OverhaulFission::Sample &x) { 100 | return x.value.nActiveCells; 101 | } 102 | 103 | static double overhaulGetEfficiency(const OverhaulFission::Sample &x) { 104 | return x.value.efficiency; 105 | } 106 | 107 | static int getIrradiatorFlux(const OverhaulFission::Sample &x) { 108 | return x.value.irradiatorFlux; 109 | } 110 | 111 | static emscripten::val overhaulGetLossHistory(const OverhaulFission::Opt &opt) { 112 | auto &data(opt.getLossHistory()); 113 | return emscripten::val(emscripten::typed_memory_view(data.size(), data.data())); 114 | } 115 | 116 | EMSCRIPTEN_BINDINGS(FissionOpt) { 117 | emscripten::class_("FissionSettings") 118 | .constructor<>() 119 | .property("sizeX", &Fission::Settings::sizeX) 120 | .property("sizeY", &Fission::Settings::sizeY) 121 | .property("sizeZ", &Fission::Settings::sizeZ) 122 | .property("fuelBasePower", &Fission::Settings::fuelBasePower) 123 | .property("fuelBaseHeat", &Fission::Settings::fuelBaseHeat) 124 | .function("setLimit", &setLimit) 125 | .function("setRate", &setRate) 126 | .property("ensureActiveCoolerAccessible", &Fission::Settings::ensureActiveCoolerAccessible) 127 | .property("ensureHeatNeutral", &Fission::Settings::ensureHeatNeutral) 128 | .property("goal", &Fission::Settings::goal) 129 | .property("symX", &Fission::Settings::symX) 130 | .property("symY", &Fission::Settings::symY) 131 | .property("symZ", &Fission::Settings::symZ); 132 | emscripten::class_("FissionSample") 133 | .function("getData", &getData) 134 | .function("getShape", &getShape) 135 | .function("getStride", &getStride) 136 | .function("getPower", &getPower) 137 | .function("getHeat", &getHeat) 138 | .function("getCooling", &getCooling) 139 | .function("getNetHeat", &getNetHeat) 140 | .function("getDutyCycle", &getDutyCycle) 141 | .function("getAvgPower", &getAvgPower) 142 | .function("getAvgBreed", &getAvgBreed) 143 | .function("getEfficiency", &getEfficiency); 144 | emscripten::class_("FissionOpt") 145 | .constructor() 146 | .function("stepInteractive", &Fission::Opt::stepInteractive) 147 | .function("needsRedrawBest", &Fission::Opt::needsRedrawBest) 148 | .function("needsReplotLoss", &Fission::Opt::needsReplotLoss) 149 | .function("getLossHistory", &getLossHistory) 150 | .function("getBest", &Fission::Opt::getBest) 151 | .function("getNEpisode", &Fission::Opt::getNEpisode) 152 | .function("getNStage", &Fission::Opt::getNStage) 153 | .function("getNIteration", &Fission::Opt::getNIteration); 154 | emscripten::class_("OverhaulFissionSettings") 155 | .constructor<>() 156 | .property("sizeX", &OverhaulFission::Settings::sizeX) 157 | .property("sizeY", &OverhaulFission::Settings::sizeY) 158 | .property("sizeZ", &OverhaulFission::Settings::sizeZ) 159 | .function("clearFuels", &clearFuels) 160 | .function("addFuel", &addFuel) 161 | .function("setLimit", &overhaulSetLimit) 162 | .function("setSourceLimit", &setSourceLimit) 163 | .property("goal", &OverhaulFission::Settings::goal) 164 | .property("controllable", &OverhaulFission::Settings::controllable) 165 | .property("symX", &OverhaulFission::Settings::symX) 166 | .property("symY", &OverhaulFission::Settings::symY) 167 | .property("symZ", &OverhaulFission::Settings::symZ); 168 | emscripten::class_("OverhaulFissionSample") 169 | .function("getData", &overhaulGetData) 170 | .function("getShape", &overhaulGetShape) 171 | .function("getStride", &overhaulGetStride) 172 | .function("getOutput", &getOutput) 173 | .function("getFuelUse", &getFuelUse) 174 | .function("getEfficiency", &overhaulGetEfficiency) 175 | .function("getIrradiatorFlux", &getIrradiatorFlux); 176 | emscripten::class_("OverhaulFissionOpt") 177 | .constructor() 178 | .function("stepInteractive", &OverhaulFission::Opt::stepInteractive) 179 | .function("needsRedrawBest", &OverhaulFission::Opt::needsRedrawBest) 180 | .function("needsReplotLoss", &OverhaulFission::Opt::needsReplotLoss) 181 | .function("getLossHistory", &overhaulGetLossHistory) 182 | .function("getBest", &OverhaulFission::Opt::getBest) 183 | .function("getNEpisode", &OverhaulFission::Opt::getNEpisode) 184 | .function("getNStage", &OverhaulFission::Opt::getNStage) 185 | .function("getNIteration", &OverhaulFission::Opt::getNIteration); 186 | } 187 | -------------------------------------------------------------------------------- /web/compile.bat: -------------------------------------------------------------------------------- 1 | em++ --bind -s MODULARIZE=1 -s EXPORT_NAME=FissionOpt -s ALLOW_MEMORY_GROWTH=1 -o FissionOpt.js -std=c++17 -flto -O3 Bindings.cpp ../Fission.cpp ../OptFission.cpp ../FissionNet.cpp ../OverhaulFission.cpp ../OptOverhaulFission.cpp ../OverhaulFissionNet.cpp -I../../xtl/include -I../../xtensor/include 2 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NuclearCraft Fission Reactor Design Generator 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |

NuclearCraft Fission Reactor Design Generator

16 | by cyb0124, source 17 |
18 |
19 | This tool generates a fission reactor design that maximizes effective power output, breeding speed or efficiency. 20 | This page is for the pre-overhaul NuclearCraft version used by many popular modpacks. 21 | For the post-overhaul version of NuclearCraft, click here. 22 |
23 |
24 |
25 |
26 | Step 1: Input the core (interior) size of the reactor: 27 | × 28 | × 29 | 30 |
31 |
32 | Step 2: Click on the fuel you want to use in the table below. Most modpacks use the default config, except for E2E and PO3. 33 | Alternatively, you can manually enter the fuel data in the textboxes below. 34 | The fuel data can be found in the tooltip of the fuel item. Do not use the data shown in JEI, they may be incorrect. 35 | You can also use IC2 and ExtremeReactors' fuels. See below the table for more details. 36 |
37 |
Fuel Base Power: RF/t
38 |
Fuel Base Heat: H/t
39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 74 | 76 | 78 | 80 | 82 | 84 | 86 | 87 | 88 | 90 | 92 | 94 | 96 | 98 | 100 | 102 | 104 | 106 | 108 | 110 | 112 | 114 | 116 | 118 | 120 | 122 | 124 | 125 | 126 | 127 | 129 | 131 | 133 | 135 | 137 | 139 | 141 | 142 | 143 | 145 | 147 | 149 | 151 | 153 | 155 | 157 | 159 | 161 | 163 | 165 | 167 | 169 | 171 | 173 | 175 | 177 | 179 | 180 | 181 | 182 | 184 | 186 | 188 | 190 | 192 | 194 | 196 | 197 | 198 | 200 | 202 | 204 | 206 | 208 | 210 | 212 | 214 | 216 | 218 | 220 | 222 | 224 | 226 | 228 | 230 | 232 | 234 | 235 |
ConfigUranium IngotTBULEU-235HEU-235LEU-233HEU-233LEN-236HEN-236MOX-239MOX-241LEP-239HEP-239LEP-241HEP-241LEA-242HEA-242LECm-243HECm-243LECm-245HECm-245LECm-247HECm-247LEB-248HEB-248LECf-249HECf-249LECf-251HECf-251
DefaultNormal 73 |
Oxide
Normal 75 |
Oxide
Normal 77 |
Oxide
Normal 79 |
Oxide
Normal 81 |
Oxide
Normal 83 |
Oxide
Normal 85 |
Oxide
LoadLoadNormal 89 |
Oxide
Normal 91 |
Oxide
Normal 93 |
Oxide
Normal 95 |
Oxide
Normal 97 |
Oxide
Normal 99 |
Oxide
Normal 101 |
Oxide
Normal 103 |
Oxide
Normal 105 |
Oxide
Normal 107 |
Oxide
Normal 109 |
Oxide
Normal 111 |
Oxide
Normal 113 |
Oxide
Normal 115 |
Oxide
Normal 117 |
Oxide
Normal 119 |
Oxide
Normal 121 |
Oxide
Normal 123 |
Oxide
E2ELoadNormal 128 |
Oxide
Normal 130 |
Oxide
Normal 132 |
Oxide
Normal 134 |
Oxide
Normal 136 |
Oxide
Normal 138 |
Oxide
Normal 140 |
Oxide
LoadLoadNormal 144 |
Oxide
Normal 146 |
Oxide
Normal 148 |
Oxide
Normal 150 |
Oxide
Normal 152 |
Oxide
Normal 154 |
Oxide
Normal 156 |
Oxide
Normal 158 |
Oxide
Normal 160 |
Oxide
Normal 162 |
Oxide
Normal 164 |
Oxide
Normal 166 |
Oxide
Normal 168 |
Oxide
Normal 170 |
Oxide
Normal 172 |
Oxide
Normal 174 |
Oxide
Normal 176 |
Oxide
Normal 178 |
Oxide
PO3Normal 183 |
Oxide
Normal 185 |
Oxide
Normal 187 |
Oxide
Normal 189 |
Oxide
Normal 191 |
Oxide
Normal 193 |
Oxide
Normal 195 |
Oxide
LoadLoadNormal 199 |
Oxide
Normal 201 |
Oxide
Normal 203 |
Oxide
Normal 205 |
Oxide
Normal 207 |
Oxide
Normal 209 |
Oxide
Normal 211 |
Oxide
Normal 213 |
Oxide
Normal 215 |
Oxide
Normal 217 |
Oxide
Normal 219 |
Oxide
Normal 221 |
Oxide
Normal 223 |
Oxide
Normal 225 |
Oxide
Normal 227 |
Oxide
Normal 229 |
Oxide
Normal 231 |
Oxide
Normal 233 |
Oxide
236 |
237 | For ExtremeReactors' Yellorium or Blutonium, click LEU-235 Normal and then click here once.
238 | For IC2's Enriched Uranium Nuclear Fuel, click LEU-235 Normal and then click here once.
239 | For IC2's MOX Nuclear Fuel, click MOX-239 and then click here once. 240 |
241 |
242 | Step 3: If you want to limit the usage of a type of block, enter the maximum number allowed in the table below. 243 | Enter zero to completely disable that block. You can hover over the acronym to see the full name of each block. 244 | Active coolers are disabled by default. You can enable them by removing the zeros. 245 |
246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 |
Block TypeWtRsQzAuGsLpDmHeEdCrFeEmCuSnMg[]##
Cooling Rate (H/t)
Max Allowed
Active Rate (H/t)
Max Active Allowed
290 |
291 | Step 4: Load the cooling rate data by clicking on one of the following modpack configs: 292 | Default 293 | E2E 294 | PO3. Alternatively, you can enter the data yourself. 295 | Passive cooling rates are in the tooltip of each cooler item. Active cooling rates are in JEI. 296 |
297 |
298 | Step 5: Adjust the options below and click “Run” to start the optimization.
299 |
300 |
301 |
302 |
303 |
304 | 305 | 306 |
307 |
308 | The most efficient reactor doesn't produce the most power. To get a efficient reactor that can still generate lots of power, 309 | you should optimize for power while limiting the cell count using the “Max Allowed” box for []. 310 | To get the ideal cell count, divide the fuel's base processing time by the time you want a fuel to last. 311 | Enabling symmetry and/or limiting block counts often speeds up the algorithm. 312 | The algorithm uses fixed seed: different runs will always yield the same results unless there is an update to the algorithm (last update: 5/19/2020). 313 | You can use Building Gadgets to automate the building of your reactor. 314 | To do so, save the JSON of your design, load it up in Hellrage's Reactor Planner, 315 | and then click “Generate BG string”. Then you can paste it in the Template Manager.
316 | Note: in Chrome, this tab won't run in background! If you want to switch to other tabs, drag this tab out as a dedicated window and don't minimize it. 317 |
318 |
319 |
320 |
321 | Run 322 | Pause 323 | Stop 324 | Save JSON 325 |
326 |
327 |
328 |
329 | 330 | 331 | -------------------------------------------------------------------------------- /web/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | line-height: 150%; 3 | } 4 | 5 | table { 6 | border-spacing: 1px; 7 | background-color: black; 8 | white-space: nowrap; 9 | } 10 | 11 | td, th { 12 | text-align: center; 13 | font-weight: normal; 14 | padding: 4px; 15 | background-color: white; 16 | } 17 | 18 | h2 { 19 | display: inline; 20 | margin-right: 1ch; 21 | } 22 | 23 | input { 24 | text-align: center; 25 | } 26 | 27 | #settings>*, main>* { 28 | margin-top: 0.3em; 29 | } 30 | 31 | .disabledLink { 32 | color: gray; 33 | cursor: not-allowed; 34 | } 35 | 36 | #sizeX, #sizeY, #sizeZ { 37 | width: 2.5ch; 38 | } 39 | 40 | #fuelBasePower, #fuelBaseHeat { 41 | width: 8.5ch; 42 | } 43 | 44 | .fuelPresets { 45 | overflow-x: auto; 46 | } 47 | 48 | #blockType>:not(:first-child) { 49 | font-family: monospace; 50 | font-weight: bold; 51 | font-size: 135%; 52 | } 53 | 54 | #rate input, #limit input, #activeRate input, #activeLimit input { 55 | width: 5.5ch; 56 | } 57 | 58 | .Wt {color: dodgerblue;} 59 | .Rs {color: red;} 60 | .Qz {color: lightgray;} 61 | .Au {color: gold;} 62 | .Gs {color: #dc0;} 63 | .Lp {color: blue;} 64 | .Dm {color: powderblue;} 65 | .He {color: lightcoral;} 66 | .Ed {color: teal;} 67 | .Cr {color: deepskyBlue;} 68 | .Fe {color: wheat;} 69 | .Em {color: green;} 70 | .Cu {color: brown;} 71 | .Sn {color: lightsteelblue;} 72 | .Mg {color: pink;} 73 | .air {color: white;} 74 | .cell {color: gray;} 75 | .mod {color: black;} 76 | 77 | #design>* { 78 | float: left; 79 | border: 1px solid black; 80 | padding: 0.5em; 81 | margin-bottom: 1em; 82 | text-align: center; 83 | font-family: sans-serif; 84 | } 85 | 86 | #design>:first-child { 87 | min-width: 20em; 88 | } 89 | 90 | .info { 91 | position: relative; 92 | } 93 | 94 | .info>:nth-child(1) { 95 | position: absolute; 96 | left: 0px; 97 | } 98 | 99 | .info>:nth-child(2) { 100 | position: absolute; 101 | right: 0px; 102 | } 103 | 104 | .row { 105 | font-family: monospace; 106 | font-weight: bold; 107 | font-size: 120%; 108 | } 109 | -------------------------------------------------------------------------------- /web/main.js: -------------------------------------------------------------------------------- 1 | $(() => { FissionOpt().then((FissionOpt) => { 2 | const run = $('#run'), pause = $('#pause'), stop = $('#stop'); 3 | let opt = null, timeout = null; 4 | 5 | const updateDisables = () => { 6 | $('#settings input').prop('disabled', opt !== null); 7 | $('#settings a')[opt === null ? 'removeClass' : 'addClass']('disabledLink'); 8 | run[timeout === null ? 'removeClass' : 'addClass']('disabledLink'); 9 | pause[timeout !== null ? 'removeClass' : 'addClass']('disabledLink'); 10 | stop[opt !== null ? 'removeClass' : 'addClass']('disabledLink'); 11 | }; 12 | 13 | const fuelBasePower = $('#fuelBasePower'); 14 | const fuelBaseHeat = $('#fuelBaseHeat'); 15 | const fuelPresets = { 16 | DefTBU: [60, 18], 17 | DefTBUO: [84, 22.5], 18 | DefLEU235: [120, 50], 19 | DefLEU235O: [168, 62.5], 20 | DefHEU235: [480, 300], 21 | DefHEU235O: [672, 375], 22 | DefLEU233: [144, 60], 23 | DefLEU233O: [201.6, 75], 24 | DefHEU233: [576, 360], 25 | DefHEU233O: [806.4, 450], 26 | DefLEN236: [90, 36], 27 | DefLEN236O: [126, 45], 28 | DefHEN236: [360, 216], 29 | DefHEN236O: [504, 270], 30 | DefMOX239: [155.4, 57.5], 31 | DefMOX241: [243.6, 97.5], 32 | DefLEP239: [105, 40], 33 | DefLEP239O: [147, 50], 34 | DefHEP239: [420, 240], 35 | DefHEP239O: [588, 300], 36 | DefLEP241: [165, 70], 37 | DefLEP241O: [231, 87.5], 38 | DefHEP241: [660, 420], 39 | DefHEP241O: [924, 525], 40 | DefLEA242: [192, 94], 41 | DefLEA242O: [268.8, 117.5], 42 | DefHEA242: [768, 564], 43 | DefHEA242O: [1075.2, 705], 44 | DefLECm243: [210, 112], 45 | DefLECm243O: [294, 140], 46 | DefHECm243: [840, 672], 47 | DefHECm243O: [1176, 840], 48 | DefLECm245: [162, 68], 49 | DefLECm245O: [226.8, 85], 50 | DefHECm245: [648, 408], 51 | DefHECm245O: [907.2, 510], 52 | DefLECm247: [138, 54], 53 | DefLECm247O: [193.2, 67.5], 54 | DefHECm247: [552, 324], 55 | DefHECm247O: [772.8, 405], 56 | DefLEB248: [135, 52], 57 | DefLEB248O: [189, 65], 58 | DefHEB248: [540, 312], 59 | DefHEB248O: [756, 390], 60 | DefLECf249: [216, 116], 61 | DefLECf249O: [302.4, 145], 62 | DefHECf249: [864, 696], 63 | DefHECf249O: [1209.6, 870], 64 | DefLECf251: [225, 120], 65 | DefLECf251O: [315, 150], 66 | DefHECf251: [900, 720], 67 | DefHECf251O: [1260, 900], 68 | E2EUraniumIngot: [600, 48], 69 | E2ETBU: [360, 21.6], 70 | E2ETBUO: [504, 27], 71 | E2ELEU235: [720, 60], 72 | E2ELEU235O: [1008, 75], 73 | E2EHEU235: [2880, 360], 74 | E2EHEU235O: [4032, 450], 75 | E2ELEU233: [864, 72], 76 | E2ELEU233O: [1209.6, 90], 77 | E2EHEU233: [3456, 432], 78 | E2EHEU233O: [4838.4, 540], 79 | E2ELEN236: [540, 43.2], 80 | E2ELEN236O: [756, 54], 81 | E2EHEN236: [2160, 259.2], 82 | E2EHEN236O: [3024, 324], 83 | E2EMOX239: [932.4, 69], 84 | E2EMOX241: [1461.6, 117], 85 | E2ELEP239: [630, 48], 86 | E2ELEP239O: [882, 60], 87 | E2EHEP239: [2520, 288], 88 | E2EHEP239O: [3528, 360], 89 | E2ELEP241: [990, 84], 90 | E2ELEP241O: [1386, 105], 91 | E2EHEP241: [3960, 504], 92 | E2EHEP241O: [5544, 630], 93 | E2ELEA242: [1152, 112.8], 94 | E2ELEA242O: [1612.8, 141], 95 | E2EHEA242: [4608, 676.8], 96 | E2EHEA242O: [6451.2, 846], 97 | E2ELECm243: [1260, 134.4], 98 | E2ELECm243O: [1764, 168], 99 | E2EHECm243: [5040, 806.4], 100 | E2EHECm243O: [7056, 1008], 101 | E2ELECm245: [972, 81.6], 102 | E2ELECm245O: [1360.8, 102], 103 | E2EHECm245: [3888, 489.6], 104 | E2EHECm245O: [5443.2, 612], 105 | E2ELECm247: [828, 64.8], 106 | E2ELECm247O: [1159.2, 81], 107 | E2EHECm247: [3312, 388.8], 108 | E2EHECm247O: [4636.8, 486], 109 | E2ELEB248: [810, 62.4], 110 | E2ELEB248O: [1134, 78], 111 | E2EHEB248: [3240, 374.4], 112 | E2EHEB248O: [4536, 468], 113 | E2ELECf249: [1296, 139.2], 114 | E2ELECf249O: [1814.4, 174], 115 | E2EHECf249: [5184, 835.2], 116 | E2EHECf249O: [7257.6, 1044], 117 | E2ELECf251: [1350, 144], 118 | E2ELECf251O: [1890, 180], 119 | E2EHECf251: [5400, 864], 120 | E2EHECf251O: [7560, 1080], 121 | PO3TBU: [18000, 72], 122 | PO3TBUO: [25200, 90], 123 | PO3LEU235: [36000, 200], 124 | PO3LEU235O: [50400, 250], 125 | PO3HEU235: [144000, 1200], 126 | PO3HEU235O: [201600, 1500], 127 | PO3LEU233: [43200, 240], 128 | PO3LEU233O: [60318, 300], 129 | PO3HEU233: [172800, 1440], 130 | PO3HEU233O: [241812, 1800], 131 | PO3LEN236: [27000, 144], 132 | PO3LEN236O: [37800, 180], 133 | PO3HEN236: [108000, 864], 134 | PO3HEN236O: [151200, 1080], 135 | PO3MOX239: [46512, 230], 136 | PO3MOX241: [72918, 390], 137 | PO3LEP239: [31500, 160], 138 | PO3LEP239O: [44100, 200], 139 | PO3HEP239: [126000, 960], 140 | PO3HEP239O: [176400, 1200], 141 | PO3LEP241: [49500, 280], 142 | PO3LEP241O: [69300, 350], 143 | PO3HEP241: [198000, 1680], 144 | PO3HEP241O: [277200, 2100], 145 | PO3LEA242: [57600, 376], 146 | PO3LEA242O: [80424, 470], 147 | PO3HEA242: [230400, 2256], 148 | PO3HEA242O: [322506, 2820], 149 | PO3LECm243: [63000, 448], 150 | PO3LECm243O: [88200, 560], 151 | PO3HECm243: [252000, 2688], 152 | PO3HECm243O: [352800, 3360], 153 | PO3LECm245: [48600, 272], 154 | PO3LECm245O: [67824, 340], 155 | PO3HECm245: [194400, 1632], 156 | PO3HECm245O: [272106, 2040], 157 | PO3LECm247: [41400, 216], 158 | PO3LECm247O: [57906, 270], 159 | PO3HECm247: [165600, 1296], 160 | PO3HECm247O: [231624, 1620], 161 | PO3LEB248: [40500, 208], 162 | PO3LEB248O: [56700, 260], 163 | PO3HEB248: [162000, 1248], 164 | PO3HEB248O: [226800, 1560], 165 | PO3LECf249: [64800, 464], 166 | PO3LECf249O: [90612, 580], 167 | PO3HECf249: [259200, 2784], 168 | PO3HECf249O: [362718, 3480], 169 | PO3LECf251: [67500, 480], 170 | PO3LECf251O: [94500, 600], 171 | PO3HECf251: [270000, 2880], 172 | PO3HECf251O: [378000, 3600] 173 | }; 174 | for (const [name, [power, heat]] of Object.entries(fuelPresets)) { 175 | $('#' + name).click(() => { 176 | if (opt !== null) 177 | return; 178 | fuelBasePower.val(power); 179 | fuelBaseHeat.val(heat); 180 | }); 181 | } 182 | const applyFuelFactor = (factor) => { 183 | if (opt !== null) 184 | return; 185 | fuelBasePower.val(fuelBasePower.val() * factor); 186 | fuelBaseHeat.val(fuelBaseHeat.val() * factor); 187 | }; 188 | $('#br').click(() => { applyFuelFactor(8 / 9); }); 189 | $('#ic2').click(() => { applyFuelFactor(18 / 19); }); 190 | $('#ic2mox').click(() => { applyFuelFactor(9 / 7); }); 191 | 192 | const rates = [], limits = []; 193 | $('#rate input').each(function() { rates.push($(this)); }); 194 | $('#activeRate input').each(function() { rates.push($(this)); }); 195 | $('#limit input').each(function() { limits.push($(this)); }); 196 | { 197 | const tail = limits.splice(-2); 198 | $('#activeLimit input').each(function() { limits.push($(this)); }); 199 | limits.push(...tail); 200 | } 201 | const loadRatePreset = (preset) => { 202 | if (opt !== null) 203 | return; 204 | $.each(rates, (i, x) => { x.val(preset[i]); }); 205 | }; 206 | $('#DefRate').click(() => { loadRatePreset([ 207 | 60, 90, 90, 120, 130, 120, 150, 140, 120, 160, 80, 160, 80, 120, 110, 208 | 150, 3200, 3000, 4800, 4000, 2800, 7000, 6600, 5400, 6400, 2400, 3600, 2600, 3000, 3600 209 | ]); }); 210 | $('#E2ERate').click(() => { loadRatePreset([ 211 | 20, 80, 80, 120, 120, 100, 120, 120, 140, 140, 60, 140, 60, 80, 100, 212 | 50, 1000, 1500, 1750, 2000, 2250, 3500, 3300, 2750, 3250, 1700, 2750, 1125, 1250, 2000 213 | ]); }); 214 | $('#PO3Rate').click(() => { loadRatePreset([ 215 | 40, 160, 160, 240, 240, 200, 240, 240, 280, 800, 120, 280, 120, 160, 200, 216 | 50, 1600, 20000, 4000, 2700, 3200, 3500, 3300, 2700, 3200, 1200, 1800, 1300, 1500, 1800 217 | ]); }); 218 | 219 | const schedule = () => { 220 | timeout = window.setTimeout(step, 0); 221 | }; 222 | 223 | const settings = new FissionOpt.FissionSettings(); 224 | const design = $('#design'); 225 | const save = $('#save'); 226 | const nCoolerTypes = 15, air = nCoolerTypes * 2 + 2; 227 | const tileNames = ['Wt', 'Rs', 'Qz', 'Au', 'Gs', 'Lp', 'Dm', 'He', 'Ed', 'Cr', 'Fe', 'Em', 'Cu', 'Sn', 'Mg', '[]', '##', '..']; 228 | const tileTitles = ['Water', 'Redstone', 'Quartz', 'Gold', 'Glowstone', 'Lapis', 'Diamond', 'Liquid Helium', 229 | 'Enderium', 'Cryotheum', 'Iron', 'Emerald', 'Copper', 'Tin', 'Magnesium', 'Reactor Cell', 'Moderator', 'Air']; 230 | $('#blockType>:not(:first)').each((i, x) => { $(x).attr('title', tileTitles[i]); }); 231 | const tileClasses = tileNames.slice(); 232 | tileClasses[15] = 'cell'; 233 | tileClasses[16] = 'mod'; 234 | tileClasses[17] = 'air'; 235 | const tileSaveNames = tileTitles.slice(0, 17); 236 | tileSaveNames[7] = 'Helium'; 237 | tileSaveNames[15] = 'FuelCell'; 238 | tileSaveNames[16] = 'Graphite'; 239 | 240 | const displayTile = (tile) => { 241 | let active = false; 242 | if (tile >= nCoolerTypes) { 243 | tile -= nCoolerTypes; 244 | if (tile < nCoolerTypes) 245 | active = true; 246 | } 247 | const result = $('' + tileNames[tile] + '').addClass(tileClasses[tile]); 248 | if (active) { 249 | result.attr('title', 'Active ' + tileTitles[tile]); 250 | result.css('outline', '2px dashed black') 251 | } else { 252 | result.attr('title', tileTitles[tile]); 253 | } 254 | return result; 255 | }; 256 | 257 | const saveTile = (tile) => { 258 | if (tile >= nCoolerTypes) { 259 | tile -= nCoolerTypes; 260 | if (tile < nCoolerTypes) { 261 | return "Active " + tileSaveNames[tile]; 262 | } 263 | } 264 | return tileSaveNames[tile]; 265 | }; 266 | 267 | const displaySample = (sample) => { 268 | design.empty(); 269 | let block = $('
'); 270 | const appendInfo = (label, value, unit) => { 271 | const row = $('
').addClass('info'); 272 | row.append('
' + label + '
'); 273 | row.append('
' + unit + '
'); 274 | row.append(Math.round(value * 100) / 100); 275 | block.append(row); 276 | }; 277 | appendInfo('Max Power', sample.getPower(), 'RF/t'); 278 | appendInfo('Heat', sample.getHeat(), 'H/t'); 279 | appendInfo('Cooling', sample.getCooling(), 'H/t'); 280 | appendInfo('Net Heat', sample.getNetHeat(), 'H/t'); 281 | appendInfo('Duty Cycle', sample.getDutyCycle() * 100, '%'); 282 | appendInfo('Fuel Use Rate', sample.getAvgBreed(), '×'); 283 | appendInfo('Efficiency', sample.getEfficiency() * 100, '%'); 284 | appendInfo('Avg Power', sample.getAvgPower(), 'RF/t'); 285 | design.append(block); 286 | 287 | const shapes = [], strides = [], data = sample.getData(); 288 | for (let i = 0; i < 3; ++i) { 289 | shapes.push(sample.getShape(i)); 290 | strides.push(sample.getStride(i)); 291 | } 292 | let resourceMap = {}; 293 | const saved = { 294 | UsedFuel: {name: '', FuelTime: 0.0, BasePower: settings.fuelBasePower, BaseHeat: settings.fuelBaseHeat}, 295 | SaveVersion: {Major: 1, Minor: 2, Build: 24, Revision: 0, MajorRevision: 0, MinorRevision: 0}, 296 | InteriorDimensions: {X: shapes[2], Y: shapes[0], Z: shapes[1]}, 297 | CompressedReactor: {} 298 | }; 299 | resourceMap[-1] = (shapes[0] * shapes[1] + shapes[1] * shapes[2] + shapes[2] * shapes[0]) * 2; 300 | for (let x = 0; x < shapes[0]; ++x) { 301 | block = $('
'); 302 | block.append('
Layer ' + (x + 1) + '
'); 303 | for (let y = 0; y < shapes[1]; ++y) { 304 | const row = $('
').addClass('row'); 305 | for (let z = 0; z < shapes[2]; ++z) { 306 | if (z) 307 | row.append(' '); 308 | const tile = data[x * strides[0] + y * strides[1] + z * strides[2]]; 309 | if (!resourceMap.hasOwnProperty(tile)) 310 | resourceMap[tile] = 1; 311 | else 312 | ++resourceMap[tile]; 313 | const savedTile = saveTile(tile); 314 | if (savedTile !== undefined) { 315 | if (!saved.CompressedReactor.hasOwnProperty(savedTile)) 316 | saved.CompressedReactor[savedTile] = []; 317 | saved.CompressedReactor[savedTile].push({X: z + 1, Y: x + 1, Z: y + 1}); 318 | } 319 | row.append(displayTile(tile)); 320 | } 321 | block.append(row); 322 | } 323 | design.append(block); 324 | } 325 | 326 | save.removeClass('disabledLink'); 327 | save.off('click').click(() => { 328 | const elem = document.createElement('a'); 329 | const url = window.URL.createObjectURL(new Blob([JSON.stringify(saved)], {type: 'text/json'})); 330 | elem.setAttribute('href', url); 331 | elem.setAttribute('download', 'reactor.json'); 332 | elem.click(); 333 | window.URL.revokeObjectURL(url); 334 | }); 335 | 336 | block = $('
'); 337 | block.append('
Total number of blocks used
') 338 | resourceMap = Object.entries(resourceMap); 339 | resourceMap.sort((x, y) => y[1] - x[1]); 340 | for (resource of resourceMap) { 341 | if (resource[0] == air) 342 | continue; 343 | const row = $('
'); 344 | if (resource[0] < 0) 345 | row.append('Casing'); 346 | else 347 | row.append(displayTile(resource[0]).addClass('row')); 348 | block.append(row.append(' × ' + resource[1])); 349 | } 350 | design.append(block); 351 | }; 352 | 353 | const progress = $('#progress'); 354 | let lossElement, lossPlot; 355 | function step() { 356 | schedule(); 357 | opt.stepInteractive(); 358 | const nStage = opt.getNStage(); 359 | if (nStage == -2) 360 | progress.text('Episode ' + opt.getNEpisode() + ', training iteration ' + opt.getNIteration()); 361 | else if (nStage == -1) 362 | progress.text('Episode ' + opt.getNEpisode() + ', inference iteration ' + opt.getNIteration()); 363 | else 364 | progress.text('Episode ' + opt.getNEpisode() + ', stage ' + nStage + ', iteration ' + opt.getNIteration()); 365 | if (opt.needsRedrawBest()) 366 | displaySample(opt.getBest()); 367 | if (opt.needsReplotLoss()) { 368 | const data = opt.getLossHistory(); 369 | while (lossPlot.data.labels.length < data.length) 370 | lossPlot.data.labels.push(lossPlot.data.labels.length); 371 | lossPlot.data.datasets[0].data = data; 372 | lossPlot.update({duration: 0}); 373 | } 374 | }; 375 | 376 | run.click(() => { 377 | if (timeout !== null) 378 | return; 379 | if (opt === null) { 380 | const parseSize = (x) => { 381 | const result = parseInt(x); 382 | if (!(result > 0)) 383 | throw Error("Core size must be a positive integer"); 384 | return result; 385 | }; 386 | const parsePositiveFloat = (name, x) => { 387 | const result = parseFloat(x); 388 | if (!(result > 0)) 389 | throw Error(name + " must be a positive number"); 390 | return result; 391 | }; 392 | try { 393 | settings.sizeX = parseSize($('#sizeX').val()); 394 | settings.sizeY = parseSize($('#sizeY').val()); 395 | settings.sizeZ = parseSize($('#sizeZ').val()); 396 | settings.fuelBasePower = parsePositiveFloat('Fuel Base Power', fuelBasePower.val()); 397 | settings.fuelBaseHeat = parsePositiveFloat('Fuel Base Heat', fuelBaseHeat.val()); 398 | settings.ensureActiveCoolerAccessible = $('#ensureActiveCoolerAccessible').is(':checked'); 399 | settings.ensureHeatNeutral = $('#ensureHeatNeutral').is(':checked'); 400 | settings.goal = parseInt($('input[name=goal]:checked').val()); 401 | settings.symX = $('#symX').is(':checked'); 402 | settings.symY = $('#symY').is(':checked'); 403 | settings.symZ = $('#symZ').is(':checked'); 404 | $.each(rates, (i, x) => { settings.setRate(i, parsePositiveFloat('Cooling Rate', x.val())); }); 405 | $.each(limits, (i, x) => { 406 | x = parseInt(x.val()); 407 | settings.setLimit(i, x >= 0 ? x : -1); 408 | }); 409 | } catch (error) { 410 | alert('Error: ' + error.message); 411 | return; 412 | } 413 | design.empty(); 414 | save.off('click'); 415 | save.addClass('disabledLink'); 416 | if (lossElement !== undefined) 417 | lossElement.remove(); 418 | const useNet = $('#useNet').is(':checked'); 419 | if (useNet) { 420 | lossElement = $('').attr('width', 1024).attr('height', 128).insertAfter(progress); 421 | lossPlot = new Chart(lossElement[0].getContext('2d'), { 422 | type: 'bar', 423 | options: {responsive: false, animation: {duration: 0}, hover: {animationDuration: 0}, scales: {xAxes: [{display: false}]}, legend: {display: false}}, 424 | data: {labels: [], datasets: [{label: 'Loss', backgroundColor: 'red', data: [], categoryPercentage: 1.0, barPercentage: 1.0}]} 425 | }); 426 | } 427 | opt = new FissionOpt.FissionOpt(settings, useNet); 428 | } 429 | schedule(); 430 | updateDisables(); 431 | }); 432 | 433 | pause.click(() => { 434 | if (timeout === null) 435 | return; 436 | window.clearTimeout(timeout); 437 | timeout = null; 438 | updateDisables(); 439 | }); 440 | 441 | stop.click(() => { 442 | if (opt === null) 443 | return; 444 | if (timeout !== null) { 445 | window.clearTimeout(timeout); 446 | timeout = null; 447 | } 448 | opt.delete(); 449 | opt = null; 450 | updateDisables(); 451 | }); 452 | }); }); 453 | -------------------------------------------------------------------------------- /web/overhaul.css: -------------------------------------------------------------------------------- 1 | body { 2 | line-height: 150%; 3 | } 4 | 5 | table { 6 | border-spacing: 1px; 7 | background-color: black; 8 | white-space: nowrap; 9 | } 10 | 11 | td, th { 12 | text-align: center; 13 | font-weight: normal; 14 | padding: 4px; 15 | background-color: white; 16 | } 17 | 18 | h2 { 19 | display: inline; 20 | margin-right: 1ch; 21 | } 22 | 23 | input { 24 | text-align: center; 25 | } 26 | 27 | #settings>*, main>* { 28 | margin-top: 0.3em; 29 | } 30 | 31 | .disabledLink { 32 | color: gray; 33 | cursor: not-allowed; 34 | } 35 | 36 | #sizeX, #sizeY, #sizeZ { 37 | width: 2.5ch; 38 | } 39 | 40 | #fuelPresets, #blockSettings { 41 | overflow-x: auto; 42 | } 43 | 44 | .name { 45 | width: 12.5ch; 46 | } 47 | 48 | .efficiency, .heat, .criticality, .limit { 49 | width: 5.5ch; 50 | } 51 | 52 | #blockTypes>:not(:first-child) { 53 | font-family: monospace; 54 | font-weight: bold; 55 | font-size: 135%; 56 | } 57 | 58 | .Wt {color: dodgerblue;} 59 | .Fe {color: wheat;} 60 | .Rs {color: red;} 61 | .Qz {color: lightgray;} 62 | .Ob {color: indigo;} 63 | .Nr {color: maroon;} 64 | .Gs {color: #dc0;} 65 | .Lp {color: blue;} 66 | .Au {color: gold;} 67 | .Pm {color: mediumaquamarine;} 68 | .Sm {color: lightgreen;} 69 | .En {color: #ecf8b4;} 70 | .Pr {color: mediumorchid;} 71 | .Dm {color: powderblue;} 72 | .Em {color: green;} 73 | .Cu {color: brown;} 74 | .Sn {color: lightsteelblue;} 75 | .Pb {color: darkslategray;} 76 | .B {color: silver;} 77 | .Li {color: lavender;} 78 | .Mg {color: pink;} 79 | .Mn {color: #aaafcc;} 80 | .Al {color: #d4f4e9;} 81 | .Ag {color: mistyrose;} 82 | .Fl {color: #8dae99;} 83 | .Vi {color: #9a7161;} 84 | .Cb {color: #9ea654;} 85 | .As {color: #8f9284;} 86 | .N {color: #42a649;} 87 | .He {color: lightcoral;} 88 | .Ed {color: teal;} 89 | .Cr {color: deepskyBlue;} 90 | .M0 {color: black;} 91 | .M1 {color: #cdd7bc;} 92 | .M2 {color: #6647d4;} 93 | .R0 {color: #c2996d;} 94 | .R1 {color: #90673b;} 95 | .air {color: white;} 96 | .other {color: gray;} 97 | 98 | #design>* { 99 | float: left; 100 | border: 1px solid black; 101 | padding: 0.5em; 102 | margin-bottom: 1em; 103 | text-align: center; 104 | font-family: sans-serif; 105 | } 106 | 107 | #design>:first-child { 108 | min-width: 20em; 109 | } 110 | 111 | .info { 112 | position: relative; 113 | } 114 | 115 | .info>:nth-child(1) { 116 | position: absolute; 117 | left: 0px; 118 | } 119 | 120 | .info>:nth-child(2) { 121 | position: absolute; 122 | right: 0px; 123 | } 124 | 125 | .row { 126 | font-family: monospace; 127 | font-weight: bold; 128 | font-size: 120%; 129 | } 130 | -------------------------------------------------------------------------------- /web/overhaul.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NuclearCraft: Overhauled Fission Reactor Design Generator 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |

NuclearCraft: Overhauled Fission Reactor Design Generator

16 | by cyb0124 17 |
18 |
This tool generates a NuclearCraft: Overhauled fission reactor design for the specified criterions. Click here for the pre-overhaul version.
19 |
20 |
21 |
22 | Interior Size: 23 | × 24 | × 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 |
Fuel IDFuel NameEfficiency (%)Heat (H/t)Criticality (N)Self PrimingMax Cells AllowedManage
New Fuel
33 |
Fuel Presets
34 |
35 | 36 | 37 |
38 |
Neutron source settings
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
Source TypeCf-252Po-BeRa-Be
Max Allowed
47 |
Block-specific settings
48 |
49 | 50 | 51 |
Block Type
Max Allowed
52 |
53 |
54 |
55 |
56 |
57 |
58 | 59 | 60 |
61 | The algorithm uses fixed seed: different runs will always yield the same results unless there is an update to the algorithm. Last update: 7/26/2020
62 | Note: in Chrome, this tab won't run in background! If you want to switch to other tabs, drag this tab out as a dedicated window and don't minimize it. 63 |
64 |
65 |
66 |
67 | Run 68 | Pause 69 | Stop 70 | Save JSON 71 |
72 |
73 |
74 |
75 | 76 | 77 | -------------------------------------------------------------------------------- /web/overhaul.js: -------------------------------------------------------------------------------- 1 | $(() => { FissionOpt().then((FissionOpt) => { 2 | const run = $('#run'), pause = $('#pause'), stop = $('#stop'); 3 | let opt = null, timeout = null; 4 | 5 | const updateDisables = () => { 6 | $('#settings input').prop('disabled', opt !== null); 7 | $('#settings a')[opt === null ? 'removeClass' : 'addClass']('disabledLink'); 8 | run[timeout === null ? 'removeClass' : 'addClass']('disabledLink'); 9 | pause[timeout !== null ? 'removeClass' : 'addClass']('disabledLink'); 10 | stop[opt !== null ? 'removeClass' : 'addClass']('disabledLink'); 11 | }; 12 | 13 | const fuelTable = $('#fuelTable'); 14 | const newFuelRow = $('#newFuelRow'); 15 | const removeFuel = (index) => { 16 | if (opt !== null) 17 | return; 18 | fuelTable.children().eq(index).remove(); 19 | const rows = fuelTable.children(); 20 | for (let i = index; i < rows.length - 1; ++i) 21 | rows.eq(i).children().first().text(i + 1); 22 | }; 23 | const addFuel = () => { 24 | if (opt !== null) 25 | return; 26 | const row = $(''); 27 | row.append('' + fuelTable.children().length + ''); 28 | row.append(''); 29 | row.append(''); 30 | row.append(''); 31 | row.append(''); 32 | row.append(''); 33 | row.append(''); 34 | row.append('Del'); 35 | row.insertBefore(newFuelRow); 36 | row.find('.del').click(() => removeFuel(row.index())); 37 | return row; 38 | }; 39 | const fuelPresets = {}; 40 | const fuelPresetHead = $('#fuelPresets tr').first(); 41 | const fuelPresetContent = $('#fuelPresets tr').last(); 42 | $('#newFuel').click(addFuel); 43 | const addFuelPreset = (type, fuel, efficiency, heat, criticality, selfPriming) => { 44 | const link = $('' + type + ''); 45 | link.click(() => { 46 | if (opt !== null) 47 | return; 48 | let name = '[' + type + ']'; 49 | if (fuel.substr(0, 3) == "MIX") 50 | name += 'M' + type + fuel.substr(3); 51 | else 52 | name += fuel; 53 | const row = addFuel(); 54 | row.find('.name').val(name); 55 | row.find('.efficiency').val(efficiency); 56 | row.find('.heat').val(heat); 57 | row.find('.criticality').val(criticality); 58 | row.find('.selfPriming').prop('checked', selfPriming); 59 | }); 60 | if (!fuelPresets.hasOwnProperty(fuel)) { 61 | fuelPresetHead.append('' + fuel + '') 62 | const content = $(''); 63 | content.append(link); 64 | fuelPresetContent.append(content); 65 | fuelPresets[fuel] = content; 66 | } else { 67 | fuelPresets[fuel].append(' ').append(link); 68 | } 69 | }; 70 | addFuelPreset("OX", "TBU", 125, 40, 234); 71 | addFuelPreset("OX", "LEU-233", 110, 216, 78); 72 | addFuelPreset("OX", "HEU-233", 115, 648, 39); 73 | addFuelPreset("OX", "LEU-235", 100, 120, 102); 74 | addFuelPreset("OX", "HEU-235", 105, 360, 51); 75 | addFuelPreset("OX", "LEN-236", 110, 292, 70); 76 | addFuelPreset("OX", "HEN-236", 115, 876, 35); 77 | addFuelPreset("OX", "LEP-239", 120, 126, 99); 78 | addFuelPreset("OX", "HEP-239", 125, 378, 49); 79 | addFuelPreset("OX", "LEP-241", 125, 182, 84); 80 | addFuelPreset("OX", "HEP-241", 130, 546, 42); 81 | addFuelPreset("OX", "MIX-239", 105, 132, 94); 82 | addFuelPreset("OX", "MIX-241", 115, 192, 80); 83 | addFuelPreset("OX", "LEA-242", 135, 390, 65); 84 | addFuelPreset("OX", "HEA-242", 140, 1170, 32); 85 | addFuelPreset("OX", "LECm-243", 145, 384, 66); 86 | addFuelPreset("OX", "HECm-243", 150, 1152, 33); 87 | addFuelPreset("OX", "LECm-245", 150, 238, 75); 88 | addFuelPreset("OX", "HECm-245", 155, 714, 37); 89 | addFuelPreset("OX", "LECm-247", 155, 268, 72); 90 | addFuelPreset("OX", "HECm-247", 160, 804, 36); 91 | addFuelPreset("OX", "LEB-248", 165, 266, 73); 92 | addFuelPreset("OX", "HEB-248", 170, 798, 36); 93 | addFuelPreset("OX", "LECf-249", 175, 540, 60, true); 94 | addFuelPreset("OX", "HECf-249", 180, 1620, 30, true); 95 | addFuelPreset("OX", "LECf-251", 180, 288, 71, true); 96 | addFuelPreset("OX", "HECf-251", 185, 864, 35, true); 97 | addFuelPreset("NI", "TBU", 125, 32, 293); 98 | addFuelPreset("NI", "LEU-233", 110, 172, 98); 99 | addFuelPreset("NI", "HEU-233", 115, 516, 49); 100 | addFuelPreset("NI", "LEU-235", 100, 96, 128); 101 | addFuelPreset("NI", "HEU-235", 105, 288, 64); 102 | addFuelPreset("NI", "LEN-236", 110, 234, 88); 103 | addFuelPreset("NI", "HEN-236", 115, 702, 44); 104 | addFuelPreset("NI", "LEP-239", 120, 100, 124); 105 | addFuelPreset("NI", "HEP-239", 125, 300, 62); 106 | addFuelPreset("NI", "LEP-241", 125, 146, 105); 107 | addFuelPreset("NI", "HEP-241", 130, 438, 52); 108 | addFuelPreset("NI", "MIX-239", 105, 106, 118); 109 | addFuelPreset("NI", "MIX-241", 115, 154, 100); 110 | addFuelPreset("NI", "LEA-242", 135, 312, 81); 111 | addFuelPreset("NI", "HEA-242", 140, 936, 40); 112 | addFuelPreset("NI", "LECm-243", 145, 308, 83); 113 | addFuelPreset("NI", "HECm-243", 150, 924, 41); 114 | addFuelPreset("NI", "LECm-245", 150, 190, 94); 115 | addFuelPreset("NI", "HECm-245", 155, 570, 47); 116 | addFuelPreset("NI", "LECm-247", 155, 214, 90); 117 | addFuelPreset("NI", "HECm-247", 160, 642, 45); 118 | addFuelPreset("NI", "LEB-248", 165, 212, 91); 119 | addFuelPreset("NI", "HEB-248", 170, 636, 45); 120 | addFuelPreset("NI", "LECf-249", 175, 432, 75, true); 121 | addFuelPreset("NI", "HECf-249", 180, 1296, 37, true); 122 | addFuelPreset("NI", "LECf-251", 180, 230, 89, true); 123 | addFuelPreset("NI", "HECf-251", 185, 690, 44, true); 124 | addFuelPreset("ZA", "TBU", 125, 50, 199); 125 | addFuelPreset("ZA", "LEU-233", 110, 270, 66); 126 | addFuelPreset("ZA", "HEU-233", 115, 810, 33); 127 | addFuelPreset("ZA", "LEU-235", 100, 150, 87); 128 | addFuelPreset("ZA", "HEU-235", 105, 450, 43); 129 | addFuelPreset("ZA", "LEN-236", 110, 366, 60); 130 | addFuelPreset("ZA", "HEN-236", 115, 1098, 30); 131 | addFuelPreset("ZA", "LEP-239", 120, 158, 84); 132 | addFuelPreset("ZA", "HEP-239", 125, 474, 42); 133 | addFuelPreset("ZA", "LEP-241", 125, 228, 71); 134 | addFuelPreset("ZA", "HEP-241", 130, 684, 35); 135 | addFuelPreset("ZA", "MIX-239", 105, 166, 80); 136 | addFuelPreset("ZA", "MIX-241", 115, 240, 68); 137 | addFuelPreset("ZA", "LEA-242", 135, 488, 55); 138 | addFuelPreset("ZA", "HEA-242", 140, 1464, 27); 139 | addFuelPreset("ZA", "LECm-243", 145, 480, 56); 140 | addFuelPreset("ZA", "HECm-243", 150, 1440, 28); 141 | addFuelPreset("ZA", "LECm-245", 150, 298, 64); 142 | addFuelPreset("ZA", "HECm-245", 155, 894, 32); 143 | addFuelPreset("ZA", "LECm-247", 155, 336, 61); 144 | addFuelPreset("ZA", "HECm-247", 160, 1008, 30); 145 | addFuelPreset("ZA", "LEB-248", 165, 332, 62); 146 | addFuelPreset("ZA", "HEB-248", 170, 996, 31); 147 | addFuelPreset("ZA", "LECf-249", 175, 676, 51, true); 148 | addFuelPreset("ZA", "HECf-249", 180, 2028, 25, true); 149 | addFuelPreset("ZA", "LECf-251", 180, 360, 60, true); 150 | addFuelPreset("ZA", "HECf-251", 185, 1080, 30, true); 151 | const Air = 40, C0 = 41, M0 = 32, R0 = 35, Shield = 37, Irradiator = 38, Conductor = 39; 152 | const cellSources = []; 153 | const tileNames = [ 154 | 'Wt', 'Fe', 'Rs', 'Qz', 'Ob', 'Nr', 'Gs', 'Lp', 'Au', 'Pm', 'Sm', 'En', 'Pr', 'Dm', 'Em', 'Cu', 155 | 'Sn', 'Pb', 'B', 'Li', 'Mg', 'Mn', 'Al', 'Ag', 'Fl', 'Vi', 'Cb', 'As', 'N', 'He', 'Ed', 'Cr', 156 | '##', '==', '--', '=)', '-)', '<>', '><', '[]', '..']; 157 | const tileTitles = [ 158 | 'Water', 'Iron', 'Redstone', 'Quartz', 'Obsidian', 'Nether Bricks', 'Glowstone', 'Lapis', 'Gold', 'Prismarine', 159 | 'Slime', 'End Stone', 'Purpur', 'Diamond', 'Emerald', 'Copper', 'Tin', 'Lead', 'Boron', 'Lithium', 'Magnesium', 160 | 'Manganese', 'Aluminum', 'Silver', 'Fluorite', 'Villiaumite', 'Carobbiite', 'Arsenic', 'Nitrogen', 'Helium', 161 | 'Enderium', 'Cryotheum', 'Graphite', 'Beryllium', 'Heavy Water', 'Beryllium-Carbon', 'Lead-Steel', 'Boron-Silver', 162 | 'Irradiator', 'Conductor', 'Air']; 163 | const tileClasses = tileNames.slice(); 164 | tileClasses[M0] = 'M0'; 165 | tileClasses[M0 + 1] = 'M1'; 166 | tileClasses[M0 + 2] = 'M2'; 167 | tileClasses[R0] = 'R0'; 168 | tileClasses[R0 + 1] = 'R1'; 169 | tileClasses[Shield] = 'other'; 170 | tileClasses[Irradiator] = 'other'; 171 | tileClasses[Conductor] = 'other'; 172 | tileClasses[Air] = 'air'; 173 | const tileSaveNames = tileTitles.slice(); 174 | tileSaveNames[5] = 'NetherBrick'; 175 | tileSaveNames[11] = 'EndStone'; 176 | tileSaveNames[M0 + 2] = 'HeavyWater'; 177 | tileSaveNames[Irradiator] = "{\"HeatPerFlux\":0,\"EfficiencyMultiplier\":0.0}"; 178 | const displayTile = (tile, pad) => { 179 | const name = tileNames[tile]; 180 | const result = $('' + name + '').addClass(tileClasses[tile]); 181 | if (pad && name.length == 1) 182 | result.append($('.').addClass('air')); 183 | result.attr('title', tileTitles[tile]); 184 | return result; 185 | }; 186 | const blockTypes = $('#blockTypes'); 187 | const blockLimits = $('#blockLimits'); 188 | for (let i = 0; i < tileNames.length - 1; ++i) { 189 | blockTypes.append($('').append(displayTile(i, false))); 190 | blockLimits.append(''); 191 | } 192 | 193 | const schedule = () => { 194 | timeout = window.setTimeout(step, 0); 195 | }; 196 | 197 | const settings = new FissionOpt.OverhaulFissionSettings(); 198 | const design = $('#design'); 199 | const save = $('#save'); 200 | 201 | const displaySample = (sample) => { 202 | design.empty(); 203 | let block = $('
'); 204 | const appendInfo = (label, value, unit) => { 205 | const row = $('
').addClass('info'); 206 | row.append('
' + label + '
'); 207 | row.append('
' + unit + '
'); 208 | row.append(Math.round(value * 100) / 100); 209 | block.append(row); 210 | }; 211 | appendInfo('Output', sample.getOutput() / 16, 'mB/t'); 212 | appendInfo('Efficiency', sample.getEfficiency() * 100, '%'); 213 | appendInfo('Fuel Use', sample.getFuelUse(), '×'); 214 | appendInfo('Irr. Flux', sample.getIrradiatorFlux(), 'N'); 215 | design.append(block); 216 | 217 | const shapes = [], strides = [], data = sample.getData(); 218 | for (let i = 0; i < 3; ++i) { 219 | shapes.push(sample.getShape(i)); 220 | strides.push(sample.getStride(i)); 221 | } 222 | 223 | let resourceMap = {}; 224 | resourceMap[-1] = (shapes[0] * shapes[1] + shapes[1] * shapes[2] + shapes[2] * shapes[0]) * 2 + (shapes[0] + shapes[1] + shapes[2]) * 4 + 8; 225 | const increaseResource = (key) => { 226 | if (!resourceMap.hasOwnProperty(key)) 227 | resourceMap[key] = 1; 228 | else 229 | ++resourceMap[key]; 230 | }; 231 | 232 | const saved = { 233 | SaveVersion: {Major: 2, Minor: 1, Build: 4, Revision: 0, MajorRevision: 0, MinorRevision: 0}, 234 | Data: { 235 | InteriorDimensions: {X: shapes[2], Y: shapes[0], Z: shapes[1]}, 236 | CoolantRecipeName: "Water to High Pressure Steam", 237 | HeatSinks: {}, 238 | Moderators: {}, 239 | Reflectors: {}, 240 | NeutronShields: {}, 241 | Irradiators: {}, 242 | Conductors: [], 243 | FuelCells: {} 244 | } 245 | }; 246 | const saveTile = (tile, x, y, z) => { 247 | let category; 248 | if (tile < M0) { 249 | category = saved.Data.HeatSinks; 250 | } else if (tile < R0) { 251 | category = saved.Data.Moderators; 252 | } else if (tile < Shield) { 253 | category = saved.Data.Reflectors; 254 | } else if (tile == Shield) { 255 | category = saved.Data.NeutronShields; 256 | } else if (tile == Irradiator) { 257 | category = saved.Data.Irradiators; 258 | } else if (tile == Conductor) { 259 | category = saved.Data.Conductors; 260 | } else if (tile == Air) { 261 | return; 262 | } else { 263 | category = saved.Data.FuelCells; 264 | } 265 | if (tile != Conductor) { 266 | const name = tileSaveNames[tile]; 267 | if (!category.hasOwnProperty(name)) 268 | category[name] = []; 269 | category = category[name]; 270 | } 271 | category.push({X: z + 1, Y: x + 1, Z: y + 1}); 272 | }; 273 | 274 | for (let x = 0; x < shapes[0]; ++x) { 275 | block = $('
'); 276 | block.append('
Layer ' + (x + 1) + '
'); 277 | for (let y = 0; y < shapes[1]; ++y) { 278 | const row = $('
').addClass('row'); 279 | for (let z = 0; z < shapes[2]; ++z) { 280 | if (z) 281 | row.append(' '); 282 | const tile = data[x * strides[0] + y * strides[1] + z * strides[2]]; 283 | row.append(displayTile(tile, true)); 284 | if (tile < Air) { 285 | increaseResource(tile); 286 | } else if (tile >= C0) { 287 | increaseResource(-2); 288 | const source = cellSources[tile - C0]; 289 | if (source) 290 | increaseResource(-2 - source); 291 | } 292 | saveTile(tile, x, y, z); 293 | } 294 | block.append(row); 295 | } 296 | design.append(block); 297 | } 298 | 299 | save.removeClass('disabledLink'); 300 | save.off('click').click(() => { 301 | const elem = document.createElement('a'); 302 | const url = window.URL.createObjectURL(new Blob([JSON.stringify(saved)], {type: 'text/json'})); 303 | elem.setAttribute('href', url); 304 | elem.setAttribute('download', 'reactor.json'); 305 | elem.click(); 306 | window.URL.revokeObjectURL(url); 307 | }); 308 | 309 | block = $('
'); 310 | block.append('
Total number of blocks used
') 311 | resourceMap = Object.entries(resourceMap); 312 | resourceMap.sort((x, y) => y[1] - x[1]); 313 | for (resource of resourceMap) { 314 | const row = $('
'); 315 | if (resource[0] == -1) 316 | row.append('Casing'); 317 | else if (resource[0] == -2) 318 | row.append('Cell'); 319 | else if (resource[0] == -3) 320 | row.append('Cf-252'); 321 | else if (resource[0] == -4) 322 | row.append('Po-Be'); 323 | else if (resource[0] == -5) 324 | row.append('Ra-Be'); 325 | else 326 | row.append(displayTile(resource[0], false).addClass('row')); 327 | block.append(row.append(' × ' + resource[1])); 328 | } 329 | design.append(block); 330 | }; 331 | 332 | const progress = $('#progress'); 333 | let lossElement, lossPlot; 334 | function step() { 335 | schedule(); 336 | opt.stepInteractive(); 337 | const nStage = opt.getNStage(); 338 | if (nStage == 1) 339 | progress.text('Episode ' + opt.getNEpisode() + ', training iteration ' + opt.getNIteration()); 340 | else if (nStage == 2) 341 | progress.text('Episode ' + opt.getNEpisode() + ', inference iteration ' + opt.getNIteration()); 342 | else 343 | progress.text('Episode ' + opt.getNEpisode() + ', rollout iteration ' + opt.getNIteration()); 344 | if (opt.needsRedrawBest()) 345 | displaySample(opt.getBest()); 346 | if (opt.needsReplotLoss()) { 347 | const data = opt.getLossHistory(); 348 | while (lossPlot.data.labels.length < data.length) 349 | lossPlot.data.labels.push(lossPlot.data.labels.length); 350 | lossPlot.data.datasets[0].data = data; 351 | lossPlot.update({duration: 0}); 352 | } 353 | }; 354 | 355 | run.click(() => { 356 | if (timeout !== null) 357 | return; 358 | if (opt === null) { 359 | const parsePositiveFloat = (name, x) => { 360 | const result = parseFloat(x); 361 | if (!(result > 0)) 362 | throw Error(name + " must be a positive number."); 363 | return result; 364 | }; 365 | const parsePositiveInt = (name, x) => { 366 | const result = parseInt(x); 367 | if (!(result > 0)) 368 | throw Error(name + " must be a positive integer."); 369 | return result; 370 | }; 371 | const parseLimit = (x) => { 372 | x = parseInt(x); 373 | return x >= 0 ? x : -1; 374 | } 375 | try { 376 | settings.sizeX = parsePositiveInt('Interior size', $('#sizeX').val()); 377 | settings.sizeY = parsePositiveInt('Interior size', $('#sizeY').val()); 378 | settings.sizeZ = parsePositiveInt('Interior size', $('#sizeZ').val()); 379 | settings.clearFuels(); 380 | while (cellSources.length) { 381 | cellSources.pop(); 382 | tileNames.pop(); 383 | tileTitles.pop(); 384 | tileClasses.pop(); 385 | tileSaveNames.pop(); 386 | } 387 | const fuels = fuelTable.children(); 388 | if (fuels.length == 1) 389 | throw Error("No fuel."); 390 | for (let i = 0; i < fuels.length - 1; ++i) { 391 | const fuel = fuels.eq(i); 392 | const selfPriming = fuel.find('.selfPriming').is(':checked'); 393 | const name = fuel.find('.name').val(); 394 | settings.addFuel( 395 | parsePositiveFloat('Efficiency', fuel.find('.efficiency').val()) / 100, 396 | parseLimit(fuel.find('.limit').val()), 397 | parsePositiveInt('Criticality', fuel.find('.criticality').val()), 398 | parsePositiveInt('Heat', fuel.find('.heat').val()), 399 | selfPriming); 400 | cellSources.push(0); 401 | tileClasses.push('other'); 402 | if (selfPriming) { 403 | tileNames.push(i + 1 + 'S'); 404 | tileTitles.push('Cell for Fuel #' + (i + 1) + ', Self-Primed'); 405 | tileSaveNames.push(name + ';True;Self'); 406 | } else { 407 | tileNames.push((i + 1).toString()); 408 | tileTitles.push('Cell for Fuel #' + (i + 1)); 409 | tileSaveNames.push(name + ';False;None'); 410 | 411 | tileClasses.push('other'); 412 | cellSources.push(1); 413 | tileNames.push(i + 1 + 'A'); 414 | tileTitles.push('Cell for Fuel #' + (i + 1) + ', Primed by Cf-252'); 415 | tileSaveNames.push(name + ';True;Cf-252'); 416 | 417 | tileClasses.push('other'); 418 | cellSources.push(2); 419 | tileNames.push(i + 1 + 'B'); 420 | tileTitles.push('Cell for Fuel #' + (i + 1) + ', Primed by Po-Be'); 421 | tileSaveNames.push(name + ';True;Po-Be'); 422 | 423 | tileClasses.push('other'); 424 | cellSources.push(3); 425 | tileNames.push(i + 1 + 'C'); 426 | tileTitles.push('Cell for Fuel #' + (i + 1) + ', Primed by Ra-Be'); 427 | tileSaveNames.push(name + ';True;Ra-Be'); 428 | } 429 | } 430 | blockLimits.find('input').each(function(i) { 431 | settings.setLimit(i, parseLimit($(this).val())); 432 | }); 433 | for (let i = 0; i < 3; ++i) 434 | settings.setSourceLimit(i, parseLimit($('#sourceLimit' + i).val())); 435 | settings.goal = parseInt($('input[name=goal]:checked').val()); 436 | settings.controllable = $('#controllable').is(':checked'); 437 | settings.symX = $('#symX').is(':checked'); 438 | settings.symY = $('#symY').is(':checked'); 439 | settings.symZ = $('#symZ').is(':checked'); 440 | } catch (error) { 441 | alert('Error: ' + error.message); 442 | return; 443 | } 444 | design.empty(); 445 | save.off('click'); 446 | save.addClass('disabledLink'); 447 | if (lossElement !== undefined) 448 | lossElement.remove(); 449 | lossElement = $('').attr('width', 1024).attr('height', 128).insertAfter(progress); 450 | lossPlot = new Chart(lossElement[0].getContext('2d'), { 451 | type: 'bar', 452 | options: {responsive: false, animation: {duration: 0}, hover: {animationDuration: 0}, scales: {xAxes: [{display: false}]}, legend: {display: false}}, 453 | data: {labels: [], datasets: [{label: 'Loss', backgroundColor: 'red', data: [], categoryPercentage: 1.0, barPercentage: 1.0}]} 454 | }); 455 | opt = new FissionOpt.OverhaulFissionOpt(settings); 456 | } 457 | schedule(); 458 | updateDisables(); 459 | }); 460 | 461 | pause.click(() => { 462 | if (timeout === null) 463 | return; 464 | window.clearTimeout(timeout); 465 | timeout = null; 466 | updateDisables(); 467 | }); 468 | 469 | stop.click(() => { 470 | if (opt === null) 471 | return; 472 | if (timeout !== null) { 473 | window.clearTimeout(timeout); 474 | timeout = null; 475 | } 476 | opt.delete(); 477 | opt = null; 478 | updateDisables(); 479 | }); 480 | }); }); 481 | --------------------------------------------------------------------------------