mirror of
https://github.com/ZmnSCPxj/clboss.git
synced 2026-08-13 12:33:20 +02:00
XRebalancer: floor=auto sweep, and log the floor ladder every cycle
The route-cost-floor option now also accepts "auto". In auto mode the driver derives the floor ladder from the live joint(N) curve each cycle and picks one rung at random, so over many Poisson-paced cycles it sweeps the whole ladder -- the high-value top rungs and the broad low rungs both get regular shots -- with no exhaustion-detection or advance-trigger state machine. A tapped tier just yields one cheap no-fill cycle and the next cycle moves on; you cannot get stuck at a walled floor. A fixed numeric floor still works exactly as before; "auto" is opt-in via setconfig. The breakpoint walk no longer stops at the floor: it captures the full joint(N) curve, the same staircase clboss-xrebalance-view prints. The ladder routine (ceiling down to the useful floor where both marginal sides still clear a net-ppm noise floor, log-spaced on the joint axis, snapped to real rows, deduped, held a minimum ratio apart) is ported verbatim from the view with identical constants, so the two agree by construction. The view stays the reference and explanatory artifact; keep them in sync. Every cycle now logs a greppable "floor levels" line listing the ladder rungs, in both auto and fixed mode, so the levels can be watched moving as balances and constraints shift. The cycle line shows the chosen floor and, in auto mode, which rung was picked. Uniform random over rungs to start (the tiny top tier is over-attended slightly but it is high-value and fails cheap, so the downside is nil); weighting is a clean future knob once there is data from uniform. Pairs with the per-part fee gate: the chosen rung sets the budget, the gate enforces it per part.
This commit is contained in:
parent
9d446e5e5a
commit
9fdfc130b4
1 changed files with 142 additions and 15 deletions
|
|
@ -70,6 +70,7 @@ private:
|
|||
double window_days;
|
||||
double fill_band;
|
||||
double drain_band;
|
||||
bool floor_auto; /* floor option set to "auto" (sweep) */
|
||||
bool started;
|
||||
|
||||
/* One row per CHANNELD_NORMAL channel, built live from
|
||||
|
|
@ -102,6 +103,7 @@ private:
|
|||
window_days = default_window_days;
|
||||
fill_band = default_fill_band;
|
||||
drain_band = default_drain_band;
|
||||
floor_auto = false;
|
||||
started = false;
|
||||
|
||||
bus.subscribe<Msg::DbResource
|
||||
|
|
@ -121,7 +123,9 @@ private:
|
|||
"Route-cost floor (ppm): stop growing the "
|
||||
"matched-pool cycle once the marginal joint "
|
||||
"NetPpm drops below this. Sets the derived "
|
||||
"amount and the maxfee budget.")
|
||||
"amount and the maxfee budget. Or \"auto\": "
|
||||
"each cycle picks a random rung of the derived "
|
||||
"floor ladder (sweep).")
|
||||
+ manifest_option(opt_attenuator, default_attenuator,
|
||||
"Fraction (0,1] of the derived matched-pool "
|
||||
"amount to actually request per cycle.")
|
||||
|
|
@ -168,6 +172,18 @@ private:
|
|||
}
|
||||
|
||||
Ev::Io<void> handle_option(Msg::Option const& o) {
|
||||
/* The floor option also accepts "auto": instead of a fixed
|
||||
* value, each cycle picks a random rung of the derived floor
|
||||
* ladder, sweeping the whole ladder over many cycles. */
|
||||
if (o.name == opt_floor && std::string(o.value) == "auto") {
|
||||
floor_auto = true;
|
||||
return Boss::log( bus, Info
|
||||
, "XRebalancer: %s set to \"auto\" "
|
||||
"(per-cycle random sweep of the floor ladder)."
|
||||
, o.name.c_str() );
|
||||
}
|
||||
if (o.name == opt_floor)
|
||||
floor_auto = false;
|
||||
double* target = nullptr;
|
||||
if (o.name == opt_per_hour) target = &per_hour;
|
||||
else if (o.name == opt_floor) target = &floor_ppm;
|
||||
|
|
@ -356,6 +372,76 @@ private:
|
|||
std::int64_t deficit; /* tgt_fill for fill, tgt_drain for drain */
|
||||
};
|
||||
|
||||
/* One point on the joint(N) curve: cumulative matched volume N and
|
||||
* the marginal fill/drain NetPpm (and their sum) admitted at that
|
||||
* depth. Mirrors the curve clboss-xrebalance-view prints. */
|
||||
struct CurvePoint {
|
||||
std::int64_t n;
|
||||
double fill_ppm;
|
||||
double drain_ppm;
|
||||
double joint;
|
||||
};
|
||||
|
||||
/* Node-agnostic floor ladder, ported from clboss-xrebalance-view and
|
||||
* kept in sync deliberately -- the view is the reference and the
|
||||
* explanatory artifact. Floors are log-spaced on the joint (= budget)
|
||||
* axis between the ceiling (top row) and the useful floor (lowest row
|
||||
* where both marginal sides still clear NOISE_PPM net), so the rung
|
||||
* count auto-scales with the node's span. Targets are snapped to real
|
||||
* curve rows, deduped, and held at least MIN_GAP apart. Constants are
|
||||
* dimensionless; see the view for the rationale. */
|
||||
std::vector<CurvePoint>
|
||||
derive_ladder(std::vector<CurvePoint> const& curve) {
|
||||
auto ladder = std::vector<CurvePoint>();
|
||||
if (curve.empty())
|
||||
return ladder;
|
||||
auto constexpr LADDER_RATIO = double(1.6);
|
||||
auto constexpr NOISE_PPM = double(10.0);
|
||||
auto constexpr MIN_GAP = double(1.25);
|
||||
auto ceiling_joint = curve[0].joint;
|
||||
auto useful_idx = std::size_t(0);
|
||||
for (auto i = std::size_t(0); i < curve.size(); ++i) {
|
||||
if (curve[i].fill_ppm >= NOISE_PPM
|
||||
&& curve[i].drain_ppm >= NOISE_PPM)
|
||||
useful_idx = i;
|
||||
else
|
||||
break;
|
||||
}
|
||||
auto useful_joint = curve[useful_idx].joint;
|
||||
auto targets = std::vector<double>();
|
||||
for (auto t = ceiling_joint; t > useful_joint; t /= LADDER_RATIO)
|
||||
targets.push_back(t);
|
||||
targets.push_back(useful_joint);
|
||||
auto already = [&ladder](std::int64_t n) {
|
||||
for (auto const& p : ladder)
|
||||
if (p.n == n)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
auto last_joint = double(-1.0);
|
||||
for (auto tgt : targets) {
|
||||
/* Snap to the row a floor=tgt would select: the
|
||||
* largest N (within the useful range) whose joint is
|
||||
* still >= tgt. */
|
||||
auto pick = curve[0];
|
||||
for (auto i = std::size_t(0); i <= useful_idx; ++i) {
|
||||
if (curve[i].joint >= tgt)
|
||||
pick = curve[i];
|
||||
else
|
||||
break;
|
||||
}
|
||||
if (already(pick.n))
|
||||
continue;
|
||||
if (last_joint > 0.0 && pick.joint > last_joint / MIN_GAP)
|
||||
continue;
|
||||
ladder.push_back(pick);
|
||||
last_joint = pick.joint;
|
||||
}
|
||||
if (!already(curve[useful_idx].n))
|
||||
ladder.push_back(curve[useful_idx]);
|
||||
return ladder;
|
||||
}
|
||||
|
||||
Ev::Io<void>
|
||||
plan_and_log( std::shared_ptr<std::vector<Chan>> chans
|
||||
, std::shared_ptr<std::map<Ln::NodeId, NetPpm>> net
|
||||
|
|
@ -425,31 +511,71 @@ private:
|
|||
std::sort(bps.begin(), bps.end());
|
||||
bps.erase(std::unique(bps.begin(), bps.end()), bps.end());
|
||||
|
||||
std::int64_t best_n = 0;
|
||||
double best_fill_ppm = 0.0, best_drain_ppm = 0.0;
|
||||
double best_joint = 0.0;
|
||||
/* Full joint(N) curve (every breakpoint), as the view computes
|
||||
* it -- we no longer stop at the floor, so the whole curve is
|
||||
* available for the ladder and for logging. */
|
||||
auto curve = std::vector<CurvePoint>();
|
||||
for (auto n : bps) {
|
||||
auto f = threshold_at(fc, n);
|
||||
auto d = threshold_at(dc, n);
|
||||
if (f < 0.0 || d < 0.0)
|
||||
break; /* one side exhausted */
|
||||
auto joint = f + d;
|
||||
if (joint >= floor_ppm) {
|
||||
best_n = n;
|
||||
best_fill_ppm = f;
|
||||
best_drain_ppm = d;
|
||||
best_joint = joint;
|
||||
curve.push_back(CurvePoint{ n, f, d, f + d });
|
||||
}
|
||||
|
||||
/* Derive the ladder every cycle (cheap) so the levels are
|
||||
* logged and can be watched moving as balances/constraints
|
||||
* shift. In "auto" mode the cut is a random rung; otherwise
|
||||
* the configured fixed floor. */
|
||||
auto ladder = derive_ladder(curve);
|
||||
auto effective_floor = floor_ppm;
|
||||
auto picked_note = std::string();
|
||||
if (floor_auto && !ladder.empty()) {
|
||||
auto dist = std::uniform_int_distribution<std::size_t>(
|
||||
0, ladder.size() - 1);
|
||||
auto idx = dist(Boss::random_engine);
|
||||
effective_floor = ladder[idx].joint;
|
||||
auto os = std::ostringstream();
|
||||
os << " (auto picked "
|
||||
<< (long long)std::llround(effective_floor) << ")";
|
||||
picked_note = os.str();
|
||||
}
|
||||
|
||||
/* One greppable line per cycle listing the ladder rungs, so the
|
||||
* levels can be tracked over time. */
|
||||
auto levels = std::ostringstream();
|
||||
levels << "XRebalancer: floor levels [" << ladder.size()
|
||||
<< " rungs]: ";
|
||||
for (auto i = std::size_t(0); i < ladder.size(); ++i) {
|
||||
if (i) levels << "/";
|
||||
levels << (long long)std::llround(ladder[i].joint);
|
||||
}
|
||||
levels << " ppm" << (floor_auto ? "" : " (fixed floor)");
|
||||
auto levels_str = levels.str();
|
||||
|
||||
/* Select the cut: largest curve row whose joint clears the
|
||||
* chosen floor (joint is non-increasing, so stop on drop). */
|
||||
std::int64_t best_n = 0;
|
||||
double best_fill_ppm = 0.0, best_drain_ppm = 0.0;
|
||||
double best_joint = 0.0;
|
||||
for (auto const& pt : curve) {
|
||||
if (pt.joint >= effective_floor) {
|
||||
best_n = pt.n;
|
||||
best_fill_ppm = pt.fill_ppm;
|
||||
best_drain_ppm = pt.drain_ppm;
|
||||
best_joint = pt.joint;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_n <= 0)
|
||||
return Boss::log( bus, Info
|
||||
return Boss::log( bus, Info, "%s", levels_str.c_str() )
|
||||
+ Boss::log( bus, Info
|
||||
, "XRebalancer: no viable cycle -- no matched "
|
||||
"volume clears floor %.1f ppm "
|
||||
"(fill=%zu drain=%zu, window=%.0fd)."
|
||||
, floor_ppm, fill.size(), drain.size()
|
||||
, effective_floor, fill.size(), drain.size()
|
||||
, window_days );
|
||||
|
||||
/* Bold set: channels accumulated to reach best_n on each side. */
|
||||
|
|
@ -472,13 +598,14 @@ private:
|
|||
* attenuator)));
|
||||
auto maxfee = std::uint32_t(std::llround(best_joint));
|
||||
|
||||
return Boss::log( bus, Info
|
||||
, "XRebalancer: cycle [flow] floor=%.1f window=%.0fd "
|
||||
return Boss::log( bus, Info, "%s", levels_str.c_str() )
|
||||
+ Boss::log( bus, Info
|
||||
, "XRebalancer: cycle [flow] floor=%.1f%s window=%.0fd "
|
||||
"-> derived N=%lld sat, joint=%.1f ppm "
|
||||
"(fill>=%.1f + drain>=%.1f); attenuator=%.3g "
|
||||
"-> request=%lld sat (maxfee %u ppm); "
|
||||
"sources=%zu dests=%zu; executing."
|
||||
, floor_ppm, window_days
|
||||
, effective_floor, picked_note.c_str(), window_days
|
||||
, (long long)best_n, best_joint
|
||||
, best_fill_ppm, best_drain_ppm
|
||||
, attenuator, (long long)requested, (unsigned)maxfee
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue