beeminder / road

Beebrain and Visual Graph Editor
http://graph.beeminder.com
Other
13 stars 3 forks source link

More elegant PPR function #239

Open dreeves opened 2 years ago

dreeves commented 2 years ago
### Desiderata
- [ ] Turn the sprawling comments into a coherent spec
- [ ] TBD

(Core ideas here by Uluc, rewritten by Dreev.)

Old PPR function:

if (r===0)     return -yaw * 2 // absolute PPR of 2 gunits if flat slope
if (yaw*r > 0) return 0 // don't let it be an OPR (optimistic presumptive)
else           return 2*r // normally PPR at twice the daily rate

New PPR function:

if (yaw < 0) return max(D, 2*r) // D>=0 so if r<0 then this gives D
else         return min(D, 2*r) // D<=0 so if r>0 then this gives D

(Or equivalently, if more opaquely: yaw * min(yaw*D, yaw*2*r))

where D (aka "daily min") is a new user-chosen parameter giving a minimum PPR. This could generalize maxflux and would let us use a consistent PPR function for all goal types. Note that setting D=0 for do-more goals gives the expected PPR=0 for that case.

Spelling out the different cases just to get a feel for it:

1. yaw>0 & dir>0 & r>=0     normal MOAR           PPR = D = 0
2. yaw>0 & dir>0 & r<0      down-sloping MOAR     PPR = 2r < 0
3. yaw<0 & dir>0 & r>=0     normal WEEN           PPR = max(D, 2r) >= 0
4. yaw<0 & dir>0 & r<0      down-sloping WEEN     PPR = D > 0
5. yaw<0 & dir<0 & r<0      normal PHAT           PPR = D > 0
6. yaw<0 & dir<0 & r>=0     up-sloping PHAT       PPR = max(D, 2r) > 0
7. yaw>0 & dir<0 & r<0      normal RASH           PPR = min(D, 2r) < 0
8. yaw>0 & dir<0 & r>=0     up-sloping RASH       PPR = D < 0

Potential problem: all-you-can-eat-buffet-hopping vacations. If you are intentionally taking a break on a weight-loss goal and having the red line slope up for a while then that's yaw<0 and r>0 which means PPRs of at least 2r. Probably you don't want that? Maybe we just assume that the right answer to that is True Breaks (gaps in the red line when you schedule a break).

Cognata

Verbata: isolines, graph aesthetics, PPR function, pessimistic presumptive reports, universal PPR, true breaks, settings vs anti-settings,

dreeves commented 2 years ago

I don't think this works but for brainstorming:

Suppose the user specified two constants -- a and b -- and the PPR were a*r+b.

The UI wouldn't have to be like "please choose a slope and y-intercept for your PPR function" (which would be a bit much even for Beeminder users) but could be like "choose either an absolute PPR or specify it as a fraction of your daily commitment amount". That's understandable enough and just imposes an additional constraint via the UI that either a or b has to be 0 but Beebrain needn't care about that.

Except a PPR of a*r+0 doesn't work when the slope is zero. That's why we added that ugly absolute 2 gunits. Forcing the user to pick both a and b nonzero is also not great.

I feel like the inelegance here means we need to rethink PPRs at a deeper level...

adamwolf commented 2 years ago

Even things like "daily commitment amount" are not super straight-forward when used over time, rather than instantaneously.

What if there's a rate that changes? How about weekends off? Not even from a programming perspective--as a user, I would have no idea how to predict what would happen when I put a number in there other than to just try it.

dreeves commented 2 years ago

Another brainstorming proposal to at least mitigate the grossness of the "absolute 2 gunits" thing is to treat that as an explicit user-chosen parameter. Hypothetical microcopy:

For do-less goals, Beeminder never presumes you did nothing if you don't report how much you did. Whatever your daily commitment, Beeminder pessimistically presumes you did twice that much if you don't enter a datapoint. Of course that doesn't work if your commitment is zero (which we don't recommend). In that case please specify how much Beeminder should presume: ____

And now that I say that, what about just enforcing strictly positive slopes on do-less graphs? You'd have to approximate do-zero with do-0.0001-per-day or something. Effectively do-zero but the PPRs would need no special case. And I don't know of any use case for negative slope segments of a do-less graph but user-mary may. We can start by seeing how plangently user-mary screams?

Related use case: An inbox whittle-down goal where you want to allow your messages to jump back up while on vacation. Or the classic all-you-can-eat-buffet-hopping vacation with weight loss. These aren't do-less goals though.

If there are important use cases that enforced strictly monotone do-less graphs would break, maybe they could be handled by True Breaks?

dreeves commented 2 years ago

What if there's a rate that changes?

The rate always means the current slope of the red line.

How about weekends off?

For do-less, those are just steep sections, so I think there's no special case to worry about with weekends-off.

as a user, I would have no idea how to predict what would happen when I put a number in there

Yeah, sounds true and like a big red flag. (As if add a parameter wasn't red flag enough!)

I'm now liking my proposal of disallowing exactly-zero or negative sloped segments. We do still need vertical segments to implement ratcheting of do-less goals. At least until we have True Breaks. But I believe vertical segments aren't a monkey wrench for isolines. I believe the slope of every point on the red line is well-defined as the limit of the slope from the right. Or is it left? Either way, vertical jumps should be fine?

PS: Staring at the code, I think "from the right" is correct. If there's a kink in the red line at time t, the slope at t is defined as the upcoming slope.

saranli commented 2 years ago

Back in the day when we were discussing this, I had recommended *ppr = min(2slope, daily_min),** which (maybe) is more understandable than an arbitrary linear function and would disallow loopholes if daily_min is forced to be above some epsilon. Disallowing horizontal segments is not quite a solution since one can always create an almost horizontal section, which would have negligible ppr due to the small slope.

dreeves commented 2 years ago

Huge thanks to Uluc for coming up with a much nicer PPR function. I've further streamlined it and generalized it, I think.

Originally I had a bunch of the following in the top-level comment of this gissue but am moving it here.

The status quo ante PPR function for drawing isolines on do-less graphs was like so:

r === 0   ? -yaw*2 : // absolute PPR of 2 gunits if flat slope
r*yaw > 0 ? 0      : // flatline if the line is sloping the wrong way
            2*r      // normally PPR at twice the daily rate

where r is the slope of the bright red line in goal units per day.

That's pretty ugly! Especially that special case of an arbitrary "2 goal units" when the slope is flat. A jump of 2 goal units could be huge or trivial depending on the goal! Also the discontinuities are not nice for drawing isolines.

Here are two alternative proposals from Uluc, where d is a user-chosen minimum PPR and r is still the daily slope of the red line.

image

Option A

yaw <  0 && r >  0 ? max(d, 2*r) : // PPR of twice daily rate but at least d
yaw >= 0 && r >  0 ? min(d, 2*r) : // (flip it around for RASH goals)
-d < r   && r <= 0 ? d+r         : // make PPR decay smoothly to zero
                     0            

Option B

yaw>0 ? min(0, d+r) : // Negative (or zero) PPRs for do-more and rationing
        max(0, d+r)   // Positive (or zero) PPRs for do-less and weight/inbox


Both options A and B ensure that you lose at least the amount d of safety buffer every day. Option B is such that you always lose exactly that much for positive slopes, whereas option A loses higher amounts of buffer for larger slopes in proportion to the slope itself.

Here's some tentative documentation-driven development for transitioning to Option B and explaining the dailymin parameter to users:

"Minimum PPR: ____ (This is a lower bound on how much Beeminder automatically presumes you did if you fail to report data. This is an anti-cranial-silicosis measure. It's crucial that you not be able to ignore a Beeminder goal. For a do-less goal like this, that means there have to be consequences for not entering data. Note that this minimum PPR is in addition to whatever the daily rate is.)"

[if user enters 0 or a number with the same sign as yaw: "Error: The minimum PPR must be enough to eventually derail you, i.e., {yaw<0 ? 'greater than' : 'less than'} zero"]

We can do much better than the above webcopy but it's convincing me that this is in fact explainable. Or the UI could be fancier and show dynamically what the actual PPR will be for the user's choice of dailymin and the current daily rate.

Transition idea: We create this field (wants a better name than dailymin -- let's call it d for now) and pre-populate it with whatever the graph's current daily rate is. Like the day we do the database update query, d becomes set to what the daily rate is on that day. That makes it match the status quo until the person's rate changes. Then we explain to them that they can adjust that d parameter.

Of course this seems to violate the anti-settings principle but, first of all, look at how much cleaner Option B is than Option A! [UPDATE: I no longer think that.] And second, it doesn't really even violate anti-settings if we just generalize the existing maxflux parameter for weight loss goals (which also solves a Massive Problem we have with weight loss goals)...

The above cases are in fact instances of the same general rule, Option B. We're getting the parameter d from the user regardless. For weight loss we call it maxflux. For sugar-eating we call it something like "minimum PPR". Regardless, the actual PPR datapoint is always r+d -- the daily rate plus d. As long as d has the opposite sign as yaw, we achieve the invariant that the PPRs always eventually derail you.

saranli commented 1 year ago

Another possibility for the user-specified daily value would be to ask for "maximum number of days allowed without data". With this, we have d=safetybuf/numdays where the safetybuf is the difference between the latest datapoint value and the redline, and numdays is the selected setting. This could be much more intuitive, in my opinion.