21#include "common/implicit_conduction.hlsli"
22#include "common/multires_conduction.hlsli"
24using namespace bicameral::fx;
25using namespace bicameral::multires;
33 constexpr int64_t
HALF_WEIGHT = int64_t{1} << (WEIGHT_SHIFT - 1);
41 {{1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1}}};
43 using CellKey = std::tuple<int32_t, int64_t, int64_t, int64_t>;
47 FxU128
Wide(uint64_t value) {
48 return {.hi = 0, .lo = value};
52 return ImWideIsZero(value);
56 return ImWideAdd(a, b);
60 return ImWideShiftRight(value, shift);
65 if (shift == 0 ||
IsZero(value))
68 FX_ASSERT(shift < 128);
70 FX_ASSERT(value.hi == 0 && (shift == 64 || (value.lo >> (128 - shift)) == 0));
71 return {.hi = value.lo << (shift - 64), .lo = 0};
74 FX_ASSERT((value.hi >> (64 - shift)) == 0);
76 return {.hi = (value.hi << shift) | (value.lo >> (64 - shift)), .lo = value.lo << shift};
80 const FxU128 low = FxMulU64Full(a.lo, b);
81 const FxU128 high = FxMulU64Full(a.hi, b);
82 FX_ASSERT(high.hi == 0);
83 const FxU128 product = {.hi = low.hi + high.lo, .lo = low.lo};
84 FX_ASSERT(product.hi >= low.hi);
90 int64_t
Weight(FxU128 numerator, FxU128 denominator) {
91 return ImWeight(numerator, denominator);
95 return ImShiftRightSigned(value, shift);
98 int64_t
FaceFlow(FxU128 coefficient, int64_t difference) {
99 return ImFaceFlow(coefficient, difference);
104 std::map<CellKey, uint32_t>
MakeCellIndex(
const std::vector<ImplicitCell>& cells) {
105 std::map<CellKey, uint32_t> index;
106 for (uint32_t i = 0; i < cells.size(); ++i) {
108 FX_ASSERT(cell.
level >= 0 && cell.
x >= 0 && cell.
y >= 0 && cell.
z >= 0 && cell.
heatCapacity != 0);
109 const bool added = index.emplace(
CellKey{cell.
level, cell.
x, cell.
y, cell.
z}, i).second;
122 std::optional<Containing>
FindContaining(
const std::map<CellKey, uint32_t>& index, int32_t level, int64_t x,
123 int64_t y, int64_t z) {
124 for (int32_t gap = 0; gap <= level; ++gap) {
125 const auto found = index.find(
CellKey{level - gap, x >> gap, y >> gap, z >> gap});
126 if (found != index.end())
127 return Containing{.index = found->second, .gap =
static_cast<uint32_t
>(gap)};
136 return WideShiftLeft(
Wide(conductance * HC_LIMIT_PER_CONDUCTANCE), 2 *
static_cast<uint32_t
>(fine.
level));
141 for (uint32_t i = 0; i < grid.
cells.size(); ++i) {
144 const int64_t x = cell.
x + offset[0];
145 const int64_t y = cell.
y + offset[1];
146 const int64_t z = cell.
z + offset[2];
147 if (x < 0 || y < 0 || z < 0)
151 if (!neighbor || (neighbor->gap == 0 && neighbor->index < i))
155 grid.
faces.push_back({.fine = i,
156 .coarse = neighbor->index,
157 .gap = neighbor->gap,
170 using Rows = std::vector<std::vector<RowEntry>>;
172 void AddToRow(std::vector<RowEntry>& row, uint32_t neighbor, FxU128 coefficient) {
174 if (entry.neighbor != neighbor)
177 entry.coefficient =
WideAdd(entry.coefficient, coefficient);
181 row.push_back({.neighbor = neighbor, .coefficient = coefficient});
185 const auto [nodeLevel, x, y, z] = key;
186 level.
levels.push_back(nodeLevel);
187 level.
x.push_back(x);
188 level.
y.push_back(y);
189 level.
z.push_back(z);
190 level.
colors.push_back(
static_cast<uint8_t
>((x + y + z) & 1));
200 for (
size_t i = 0; i < count; ++i) {
202 for (
const RowEntry& entry : rows[i])
203 diagonal =
WideAdd(diagonal, entry.coefficient);
207 for (
const RowEntry& entry : rows[i]) {
208 level.
neighbors.push_back(entry.neighbor);
209 level.
weights.push_back(
Weight(entry.coefficient, diagonal));
226 rows[face.
coarse].push_back(
227 {.neighbor = face.
fine,
237 return std::ranges::any_of(level.
selfWeights, [](int64_t weight) { return weight < HALF_WEIGHT; });
242 const int32_t finest = *std::ranges::max_element(fine.
levels);
243 const size_t count = fine.
levels.size();
245 std::map<CellKey, uint32_t> index;
246 std::vector<uint8_t> lowered;
248 for (
size_t i = 0; i < count; ++i) {
249 const bool lower = fine.
levels[i] == finest;
250 const CellKey key = lower ?
CellKey{finest - 1, fine.
x[i] >> 1, fine.
y[i] >> 1, fine.
z[i] >> 1}
252 const auto [found, added] = index.emplace(key,
static_cast<uint32_t
>(coarse.
levels.size()));
255 lowered.push_back(lower ? 1 : 0);
258 const uint32_t parent = found->second;
266 for (
size_t i = 0; i < count; ++i) {
267 const uint32_t parent = fine.
parents[i];
276 for (
size_t parent = 0; parent < rows.size(); ++parent) {
277 for (
RowEntry& entry : rows[parent]) {
278 if (!galerkin && (lowered[parent] != 0 || lowered[entry.neighbor] != 0))
285 for (
size_t i = 0; i < count; ++i) {
298 const std::vector<int64_t>& values,
size_t node) {
299 int64_t sum = rhs[node];
308 std::vector<int64_t>& values, std::vector<int64_t>& next) {
309 for (
size_t i = 0; i < values.size(); ++i) {
310 if (level.
colors[i] == color)
311 next[i] =
Relax(level, rhs, values, i);
314 for (
size_t i = 0; i < values.size(); ++i) {
315 if (level.
colors[i] == color)
323 std::vector<int64_t> next(values.size());
324 for (uint32_t sweep = 0; sweep < sweeps; ++sweep) {
325 for (uint8_t color = 0; color <
COLOR_COUNT; ++color)
336 if (depth + 1 == grid.
levels.size()) {
345 std::vector<int64_t> coarseRhs(coarse.
levels.size(), 0);
346 for (
size_t i = 0; i < values.size(); ++i) {
347 const int64_t residual =
Relax(level, rhs, values, i) - values[i];
355 std::vector<int64_t> correction(coarse.
levels.size(), 0);
356 VCycle(grid, depth + 1, coarseRhs, correction, options, cost);
357 for (
size_t i = 0; i < values.size(); ++i)
368 const std::vector<int64_t>& values, uint32_t toleranceMillikelvin,
ImplicitCost& cost) {
369 bool converged =
true;
370 for (
size_t i = 0; i < values.size(); ++i) {
371 if (ImExceedsTolerance(
Relax(cells, rhs, values, i) - values[i], cells.
selfWeights[i],
372 toleranceMillikelvin))
385 std::vector<int64_t> temperatures(grid.
cells.size());
386 for (
size_t i = 0; i < grid.
cells.size(); ++i)
387 temperatures[i] = grid.
cells[i].startTemperature >= 0 ? grid.
cells[i].startTemperature
393 std::vector<int64_t>
Flows(
const ImplicitGrid& grid,
const std::vector<int64_t>& temperatures) {
394 std::vector<int64_t> flows(grid.
faces.size());
395 for (
size_t f = 0; f < grid.
faces.size(); ++f) {
405 for (
size_t f = 0; f < grid.
faces.size(); ++f) {
411 coarse.
energy += flows[f];
415 const MrCrossTransfer transfer = MrSplitCrossFlow(flows[f], face.
gap, coarse.
coarseFraction);
416 const MrEnergyDelta sum = MrAddEnergyDelta({.whole = coarse.
energy, .fraction = coarse.
fraction},
417 transfer.coarseDelta);
418 fine.
energy += transfer.fineDelta;
419 coarse.
energy = sum.whole;
433 return ImAddClamped(a, b);
438 std::vector<int64_t> flows(grid.
faces.size());
439 for (
size_t f = 0; f < grid.
faces.size(); ++f) {
441 const FxU128 coefficient = ImExplicitCoefficient(face.
coefficient, grid.
cells[face.
fine].heatCapacity,
457 const auto [lowestStart, highestStart] = std::ranges::minmax(start);
464 for (
size_t i = 0; i < grid.
cells.size(); ++i) {
465 const uint64_t capacity = grid.
cells[i].heatCapacity;
476 std::vector<int64_t> energies(grid.
cells.size());
477 for (
size_t i = 0; i < grid.
cells.size(); ++i)
478 energies[i] = grid.
cells[i].energy;
480 for (
size_t f = 0; f < grid.
faces.size(); ++f) {
492 return ImOverrun(energy, bounds.
lowest[i], bounds.
highest[i]);
498 bool changed =
false;
499 for (
size_t i = 0; i < grid.
cells.size(); ++i) {
500 const int64_t over =
Overrun(energies[i], bounds, i);
502 const int64_t excess = ImExcessMillikelvin(over, grid.
cells[i].heatCapacity);
506 if (limited[i] != 0 || over <= bounds.
slack[i])
520 std::vector<int64_t> explicitFlows;
521 std::vector<uint8_t> limited(grid.
cells.size(), 0);
529 if (explicitFlows.empty())
532 for (
size_t f = 0; f < grid.
faces.size(); ++f) {
534 if (limited[face.
fine] != 0 || limited[face.
coarse] != 0)
535 flows[f] = explicitFlows[f];
554 return {.numerator = 1, .denominator = 3};
556 return {.numerator = (j * j) + j - 2, .denominator = 2 * j * (j + 1)};
567 const int64_t weight =
Weight(numerator, denominator);
569 return negative ? -weight : weight;
573 const uint64_t s = stageCount;
574 const uint64_t span = (s * s) + s - 2;
575 std::vector<Rkl2Stage> stages(stageCount + 1);
577 for (uint64_t j = 2; j <= s; ++j) {
602 for (
size_t i = 0; i < cells.
capacities.size(); ++i) {
603 FxU128 sum =
Wide(0);
611 while ((((s * s) + s - 2) <<
STAGE_RATIO_SHIFT) < 4 *
static_cast<uint64_t
>(worst))
614 return static_cast<uint32_t
>(s);
619 const std::vector<int64_t>& accumulated) {
620 std::vector<int64_t> heat(grid.
cells.size(), 0);
621 for (
size_t f = 0; f < grid.
faces.size(); ++f) {
625 heat[face.
fine] -= scaled;
629 std::vector<int64_t> temperatures(grid.
cells.size());
630 for (
size_t i = 0; i < grid.
cells.size(); ++i) {
631 const auto capacity =
static_cast<int64_t
>(grid.
cells[i].heatCapacity);
632 temperatures[i] = start[i] + FxDivShiftS64(heat[i], capacity, 32);
645 const std::vector<Rkl2Stage> stages =
Rkl2Stages(stageCount);
646 const std::vector<int64_t> startFlows =
Flows(grid, start);
647 const size_t faceCount = grid.
faces.size();
648 std::vector<int64_t> before(faceCount, 0);
649 std::vector<int64_t> previous(faceCount);
650 for (
size_t f = 0; f < faceCount; ++f)
651 previous[f] =
MulWeight(stages[1].muTilde, startFlows[f]);
653 for (uint32_t j = 2; j <= stageCount; ++j) {
656 std::vector<int64_t> current(faceCount);
657 for (
size_t f = 0; f < faceCount; ++f) {
662 before = std::move(previous);
663 previous = std::move(current);
667 cost.
passes += 2 *
static_cast<uint64_t
>(stageCount);
677 grid.
cells = std::move(cells);
685 const uint64_t maxNodes = limits.
nodes == 0 ? UINT64_MAX : limits.
nodes;
686 const uint64_t maxLinks = limits.
links == 0 ? UINT64_MAX : limits.
links;
688 grid.
cells = std::move(cells);
689 grid.
faces = std::move(faces);
690 grid.
levels.push_back(MakeCellLevel(grid));
691 uint64_t nodes = grid.
levels.back().levels.size();
692 uint64_t links = grid.
levels.back().neighbors.size();
693 while (grid.
levels.size() < MAX_GRID_LEVELS && NeedsCoarser(grid.
levels.back())) {
695 if (coarser.
levels.size() == grid.
levels.back().levels.size())
699 const uint64_t lastLinks = grid.
levels.back().neighbors.size();
700 if (nodes + coarser.
levels.size() > maxNodes || links + lastLinks > maxLinks)
703 nodes += coarser.
levels.size();
705 grid.
levels.push_back(std::move(coarser));
713 const std::vector<int64_t> start = Temperatures(grid);
715 std::vector<int64_t> flows = Rkl2Flows(grid, start, cost);
717 ApplyFlows(grid, flows);
724 std::vector<int64_t> rhs(start.size());
725 for (
size_t i = 0; i < start.size(); ++i)
726 rhs[i] = MulWeight(cells.
selfWeights[i], start[i]);
728 std::vector<int64_t> solution = start;
730 Smooth(cells, rhs, solution, options.
sweeps, cost);
732 for (uint32_t cycle = 0; cycle < options.
cycles; ++cycle) {
733 VCycle(grid, 0, rhs, solution, options, cost);
741 std::vector<int64_t> flows = Flows(grid, solution);
743 ApplyFlows(grid, flows);
754 return ImEnergyFor(heatCapacity, millikelvin);
760 finest = std::max(finest, cell.
level);
763 FxU128 total = Wide(0);
765 const auto shift =
static_cast<uint32_t
>(finest - cell.
level) * ENERGY_BITS_PER_LEVEL;
766 FX_ASSERT(shift < 64);
767 const auto whole =
static_cast<uint64_t
>(cell.
energy);
768 const FxU128 value = shift == 0 ? FxU128{.hi = whole, .lo = cell.
fraction}
769 : FxU128{.hi = (whole << shift) | (cell.
fraction >> (64 - shift)),
771 const FxU128 sum = {.hi = total.hi + value.hi, .lo = total.lo + value.lo};
772 total = {.hi = sum.hi + (sum.lo < total.lo ? 1u : 0u), .lo = sum.lo};
int64_t MulWeight(int64_t weight, int64_t value)
std::map< CellKey, uint32_t > MakeCellIndex(const std::vector< ImplicitCell > &cells)
bool Converged(const ImplicitGridLevel &cells, const std::vector< int64_t > &rhs, const std::vector< int64_t > &values, uint32_t toleranceMillikelvin, ImplicitCost &cost)
constexpr uint32_t CORRECTION_SCALE_SHIFT
std::vector< int64_t > Flows(const ImplicitGrid &grid, const std::vector< int64_t > &temperatures)
int64_t Overrun(int64_t energy, const Bounds &bounds, size_t i)
constexpr uint32_t ENERGY_BITS_PER_LEVEL
FxU128 WideShiftLeft(FxU128 value, uint32_t shift)
constexpr int64_t HALF_WEIGHT
Bounds MakeBounds(const ImplicitGrid &grid, const std::vector< int64_t > &start, int64_t slackMillikelvin)
FxU128 WideMul(FxU128 a, uint64_t b)
void LimitFlows(const ImplicitGrid &grid, const std::vector< int64_t > &start, int64_t slackMillikelvin, std::vector< int64_t > &flows, ImplicitCost &cost)
void FinishLevel(ImplicitGridLevel &level, const Rows &rows)
FxU128 Wide(uint64_t value)
int64_t FaceFlow(FxU128 coefficient, int64_t difference)
int64_t Relax(const ImplicitGridLevel &level, const std::vector< int64_t > &rhs, const std::vector< int64_t > &values, size_t node)
void PushNode(ImplicitGridLevel &level, const CellKey &key, FxU128 capacity)
std::vector< std::vector< RowEntry > > Rows
FxU128 WideAdd(FxU128 a, FxU128 b)
Rational Rkl2B(uint64_t j)
FxU128 FaceCoefficient(const ImplicitCell &fine, const ImplicitCell &coarse)
bool NeedsCoarser(const ImplicitGridLevel &level)
ImplicitGridLevel MakeCellLevel(const ImplicitGrid &grid)
int64_t RationalWeight(FxU128 numerator, FxU128 denominator, bool negative)
void VCycle(const ImplicitGrid &grid, size_t depth, const std::vector< int64_t > &rhs, std::vector< int64_t > &values, const ImplicitOptions &options, ImplicitCost &cost)
constexpr uint32_t STAGE_RATIO_SHIFT
int64_t AddClamped(int64_t a, int64_t b)
FxU128 WideShiftRight(FxU128 value, uint32_t shift)
std::tuple< int32_t, int64_t, int64_t, int64_t > CellKey
void AddToRow(std::vector< RowEntry > &row, uint32_t neighbor, FxU128 coefficient)
std::vector< int64_t > Temperatures(const ImplicitGrid &grid)
constexpr uint32_t WEIGHT_SHIFT
bool MarkOutside(const ImplicitGrid &grid, const Bounds &bounds, const std::vector< int64_t > &energies, std::vector< uint8_t > &limited, ImplicitCost &cost)
constexpr uint8_t COLOR_COUNT
std::vector< int64_t > ExplicitFlows(const ImplicitGrid &grid, const std::vector< int64_t > &start)
std::vector< int64_t > Rkl2Flows(const ImplicitGrid &grid, const std::vector< int64_t > &start, ImplicitCost &cost)
void SweepColor(const ImplicitGridLevel &level, const std::vector< int64_t > &rhs, uint8_t color, std::vector< int64_t > &values, std::vector< int64_t > &next)
int64_t Weight(FxU128 numerator, FxU128 denominator)
uint32_t Rkl2StageCount(const ImplicitGridLevel &cells)
constexpr std::array< std::array< int64_t, 3 >, 6 > FACE_OFFSETS
void FindFaces(ImplicitGrid &grid)
std::vector< int64_t > StageTemperatures(const ImplicitGrid &grid, const std::vector< int64_t > &start, const std::vector< int64_t > &accumulated)
int64_t ShiftRightSigned(int64_t value, uint32_t shift)
constexpr uint32_t TEMPERATURE_SHIFT
ImplicitGridLevel Coarsen(ImplicitGridLevel &fine, bool galerkin)
std::optional< Containing > FindContaining(const std::map< CellKey, uint32_t > &index, int32_t level, int64_t x, int64_t y, int64_t z)
void ApplyFlows(ImplicitGrid &grid, const std::vector< int64_t > &flows)
std::vector< int64_t > EnergiesAfter(const ImplicitGrid &grid, const std::vector< int64_t > &flows)
bool IsZero(FxU128 value)
void Smooth(const ImplicitGridLevel &level, const std::vector< int64_t > &rhs, std::vector< int64_t > &values, uint32_t sweeps, ImplicitCost &cost)
std::vector< Rkl2Stage > Rkl2Stages(uint32_t stageCount)
constexpr size_t MAX_GRID_LEVELS
FxU128 ImplicitConservedTotal(const ImplicitGrid &grid)
ImplicitCost StepImplicit(ImplicitGrid &grid, const ImplicitOptions &options)
ImplicitGrid BuildImplicitGrid(std::vector< ImplicitCell > cells, bool galerkin)
int64_t ImplicitTemperature(const ImplicitCell &cell)
int64_t ImplicitEnergyFor(uint64_t heatCapacity, int64_t millikelvin)
int64_t worstExcessMillikelvin
std::vector< int32_t > levels
std::vector< uint32_t > neighbors
std::vector< int64_t > weights
std::vector< fx::FxU128 > capacities
std::vector< uint32_t > parents
std::vector< fx::FxU128 > diagonals
std::vector< int64_t > selfWeights
std::vector< uint32_t > rowStarts
std::vector< uint8_t > colors
std::vector< int64_t > restrictWeights
std::vector< fx::FxU128 > coefficients
std::vector< ImplicitGridLevel > levels
std::vector< ImplicitFace > faces
std::vector< ImplicitCell > cells
uint32_t toleranceMillikelvin
uint32_t limitSlackMillikelvin
std::vector< int64_t > lowest
std::vector< int64_t > highest
std::vector< int64_t > slack