WouterJD / FortiusANT

FortiusANT enables a pre-smart Tacx trainer (usb- or ANT-connected) to communicate with TrainerRoad, Rouvy or Zwift through ANT or Bluetooth LE.
GNU General Public License v3.0
154 stars 78 forks source link

Improve Virtual Speed in ERGmode #175

Closed WouterJD closed 3 years ago

WouterJD commented 3 years ago

@WouterJD @torpedox88 ... a quick, related after-note because I've just discovered this... thanks to @ericchristoffersen

GoldenCheetah has a sim model which correctly uses power over time to calculate a virtual bike speed. A recent change was made specifically to use this model in ERG mode, assuming a slope of 0%, so that ERG mode workouts have a consistent speed and distance.

Such a model shouldn't be difficult to implement (I've played with one) and could well be a little enhancement for FortiusANT, to replace the inverse calculation, which has the same aim, just the maths is different.

https://github.com/GoldenCheetah/GoldenCheetah/issues/3712 https://groups.google.com/g/golden-cheetah-users/c/o3_0vHeGc1c/m/n0490j0aCAAJ

Originally posted by @mattipee in https://github.com/WouterJD/FortiusANT/issues/125#issuecomment-748695964

WouterJD commented 3 years ago

Please suggest how to proceed

mattipee commented 3 years ago

Thanks, Wouter.

I'll make some quick notes now, and come back to this at some point. It's low priority.

https://physics.stackexchange.com/questions/226854/how-can-i-model-the-acceleration-velocity-of-a-bicycle-knowing-only-the-power-ou/226856

Link above has example python code which I played with to good effect, and nice that it graphs. But the fundamentals are captured in this snippet from one of the comments...

Screenshot_20201221-090917~2.png

It's easier than it looks.

mattipee commented 3 years ago

I think there's a typo in the snippet... Pneeded = Ftotal . v

mattipee commented 3 years ago

We have the function to calculate Pneeded already, it just needs to be parameterised (or copy-paste-modify 🤦‍♂️) so we can call it with the model's current sim-speed and grade 0%.

switchabl commented 3 years ago

Some notes:

We have the function to calculate Pneeded already, it just needs to be parameterised (or copy-paste-modify 🤦‍♂️) [...]

The former, I think. There are several class methods that do straightforward calculations, but instead of taking their inputs as parameters and returning the result, they operate on member variables. That makes it harder to follow the data flow and harder to re-use. I would move them to a separate utility/maths module instead. If necessary, number of parameters passed around can be reduced by making a new "SimulationParameters" class grouping slope, rolling resistance, wind speed etc together.

mattipee commented 3 years ago

@switchabl totally agree on the refactor. Decouple, decouple...

Other notes also good and appreciated.

mjunkmyjunk commented 3 years ago

I'm looking at the code specifically on https://github.com/WouterJD/FortiusANT/blob/ec1988dde777a7c484b8d3580748af6c333eb02b/pythoncode/usbTrainer.py#L863

whereby when power = 0, then vSpeed = 0 and wondering if this is related to this current enhancement being discussed?

Even tho during indoor trainer session, there is technically no coasting, It's not really representative of real world conditions, even the trainer flywheel still do turn after the user stop pedalling.

thanks...

WouterJD commented 3 years ago

Hi @mjunkmyjunk; interesting observation. This code is created with ERGmode training in mind so you are probably right. Code improvement suggestions welcome.

Welcome to the FortiusANT community


I'm always curious to know who I communicate with, where FortiusANT is used and what configuration is used. Please tell me what bundle did you buy, and what brake and what head unit do you use? I would therefore appreciate that you introduce yourself; perhaps leave a comment under issue #14.


mjunkmyjunk commented 3 years ago

@WouterJD ah.. in ergmode, slope is always 0% so that would be all right.

I was looking at this https://physics.stackexchange.com/questions/613209/calculating-speed-of-bicycle-down-a-hill-or-coasting-to-a-stop which does offer a physics based coastdown speed towards 0kmh from the initial velocity when user stops pedalling.

However, the equation doesn't work for slopes greater than -0.3%.

There's also this https://physics.stackexchange.com/questions/334654/how-to-correctly-determine-the-stopping-distance-of-a-coasting-bicycle-when-cons/334968?noredirect=1#comment1383300_334968 but again, part of the equation would come out as "error" due to a SQRT(-ve) number.

GoldenCheetah's code may be the best, but I am not well versed in it and can't get a good grasp of the implementation (esp this part of the solver code - https://github.com/GoldenCheetah/GoldenCheetah/pull/3064)

ericchristoffersen commented 3 years ago

GoldenCheetah's code may be the best, but I am not well versed in it and can't get a good grasp of the implementation (esp this part of the solver code - GoldenCheetah/GoldenCheetah#3064)

I can maybe shed light on that gc code and your current situation.

First, that golden cheetah 'virtual speed' is the speed at which forces are balanced, it is the speed at which bicycle is neither accelerating or decelerating. The virtual speed is the speed the bicycle will converge to if your power and the bicycle state are kept constant. I don't think the virtual speed in gc is useful for anyone and should be deleted because its just confusing. If you try to use it for a simulated ride it will both over and underreport speed because it literally ignores acceleration. It would be your speed if you had no inertia.

The reason the golden cheetah virtual speed implementation is so complicated is because it is discovering the v term of the force equation, which is to the third power, otherwise known as a cubic. The old gc implementation had a closed form expression for v which can only be valid when the terms were in what we might call 'the climbing zone.' When I tried to use it for general simulation I was also descending and the previous closed form expression would produce nan and infinity because the values pushed the cubic into a different solution zone where the existing closed form is invalid.

If you're interested the Jim Blinn paper explains everything there is to know about solving cubics:

https://courses.cs.washington.edu/courses/cse590b/13au/lecture_notes/solvecubic_p5.pdf

I think one of the best papers I've ever read. Spend a few days to understand it, its very clever stuff. Probably the best fp precision management I've ever seen in a paper.

So... you almost certainly don't want that virtual speed calculation. Its not a good number unless for some reason you really wish 'speed at the limit'.

If you wish to simulate speed independent of trainer speed you will need to sum force over time. You should look at the SampleSpeed method at the end of bicyclesim.cpp. That integrates power over time and applies it to the simulated bicycle. That is how to correctly model a virtual bicycle's speed over time. Because the input power value isn't continuous the key is in how the integration works. I left a bunch of different integrators in the source so you can see compare how they behave. I used the kahan/li because it converges on correct so quickly that it is like magic.

mjunkmyjunk commented 3 years ago

First, that golden cheetah 'virtual speed' is the speed at which forces are balanced, it is the speed at which bicycle is neither accelerating or decelerating. [snip] It would be your speed if you had no inertia.

Yes. I realise that although parts of your code also references KE values which would be the Kinetic Energy stored in the "system". (ref: https://groups.google.com/g/golden-cheetah-users/c/G7yYcXf-UD4)

The reason the golden cheetah virtual speed implementation is so complicated is because it is discovering the v term of the force equation, which is to the third power, otherwise known as a cubic.

Is this the same "virtual speed" which you are referencing and suggest needs to be deleted?

If you're interested the Jim Blinn paper explains everything there is to know about solving cubics: https://courses.cs.washington.edu/courses/cse590b/13au/lecture_notes/solvecubic_p5.pdf

I tried reading it. (emphasis on tried.)

If you wish to simulate speed independent of trainer speed you will need to sum force over time. You should look at the SampleSpeed method at the end of bicyclesim.cpp. That integrates power over time and applies it to the simulated bicycle. That is how to correctly model a virtual bicycle's speed over time. Because the input power value isn't continuous the key is in how the integration works. I left a bunch of different integrators in the source so you can see compare how they behave. I used the kahan/li because it converges on correct so quickly that it is like magic.

yeah, I also looked at the sampleSpeed code and that of MotionStatePair and quite honestly, GC's code is based on C and my proficiency isn't quite up to par. https://github.com/GoldenCheetah/GoldenCheetah/blob/2d64166acd61612cba6684cd27d60cab813667f5/src/Train/BicycleSim.cpp#L457

My Simplistic attempt at modelling coasting downhill. (using this https://github.com/WouterJD/FortiusANT/issues/175#issuecomment-748865755 to somewhat calculate for the value of a(acceleration))

Screenshot 2021-02-18 at 10 51 29 AM

It definitely can be made better, but now at least, I won't severely underreport my downhill coasting speed. (prev it was power = 0, speed = 0). However, like you said, the model is incomplete when we're looking at WattsForV and this is clearly evident when I model the user to start pedaling again. It goes from 35kmh at x seconds to 4kmh at x+1 sec when user applies a 50w power to the pedals. (50w at 5% gradient = 4w in my model). Inertia and KE is not taken into account.

ericchristoffersen commented 3 years ago

The reason the golden cheetah virtual speed implementation is so complicated is because it is discovering the v term of the force equation, which is to the third power, otherwise known as a cubic.

Is this the same "virtual speed" which you are referencing and suggest needs to be deleted?

Yes, exactly. I tried my best to do a perfect job on the virtual speed thing that is bad and should be deleted. At the time I was just getting started and didn't realize the closed form approach was completely wrong.

If you're interested the Jim Blinn paper explains everything there is to know about solving cubics: https://courses.cs.washington.edu/courses/cse590b/13au/lecture_notes/solvecubic_p5.pdf

I tried reading it. (emphasis on tried.)

Its really good. You'll learn a lot. There are 5 papers, start with the first and go slow. You'll learn a lot.

yeah, I also looked at the sampleSpeed code and that of MotionStatePair and quite honestly, GC's code is based on C and my proficiency isn't quite up to par. https://github.com/GoldenCheetah/GoldenCheetah/blob/2d64166acd61612cba6684cd27d60cab813667f5/src/Train/BicycleSim.cpp#L457

Why don't you simply run it in debugger? Step through and see what it does. I promise the real magic is in that ::Integrate call. Hamiltonian symplectic integrator. It is incredible technology. That way of summing is able to predict and account for the change that must have occurred between the datapoints.

My Simplistic attempt at modelling coasting downhill. (using this #175 (comment) to somewhat calculate for the value of a(acceleration))

Screenshot 2021-02-18 at 10 51 29 AM

It definitely can be made better, but now at least, I won't severely underreport my downhill coasting speed. (prev it was power = 0, speed = 0). However, like you said, the model is incomplete when we're looking at WattsForV and this is clearly evident when I model the user to start pedaling again. It goes from 35kmh at x seconds to 4kmh at x+1 sec when user applies a 50w power to the pedals. (50w at 5% gradient = 4w in my model). Inertia and KE is not taken into account.

Sure that is better but I don't understand why you'd stop before you had it correct. Just copy the gc code.

mjunkmyjunk commented 3 years ago

Why don't you simply run it in debugger? Step through and see what it does. I promise the real magic is in that ::Integrate call. Hamiltonian symplectic integrator. It is incredible technology. That way of summing is able to predict and account for the change that must have occurred between the datapoints.

TBH - I've yet to figure out how exactly to set up the IDE/Enviroment for my Mac.

Sure that is better but I don't understand why you'd stop before you had it correct. Just copy the gc code.

Once I learn how to compile it for Mac.

switchabl commented 3 years ago

I have a somewhat different view than @ericchristoffersen (I think; at least I don't believe steady-state virtual speed should be removed altogether). Before I elaborate let me emphasize that there is not necessarily a right answer here. Virtual speed is a compromise that we make because the limited resistance range and (in some cases) inertia of the trainer doesn't allow for a realistic simulation in all situations. So this is as much about user/ride experience as it is about physics. With that out of the way, I think there are at least three different kinds of speeds that are potentially useful:

One detail I glossed over so far is the input power that is fed into calculation. For the above to work correctly, we also need to use slightly different power definitions:

As for the numerical aspects, my personal impulse would be to stick to something simple because I expect neither accuracy nor performance to be critical here (and I am not sure that higher order methods necessarily improve performance with noisy measurement data where smoothness assumptions may not apply). But I haven't really looked into that, so I will defer to @ericchristoffersen on this.

mjunkmyjunk commented 3 years ago

Thank you for your thought contribution on this matter. It is really interesting to read through it all. You are absolutely right in the toss-up/balance between user experience (feel) and real world physics.

Indoors, the wheel speed would not necessarily correlate to actual speed outdoors whether it's a wheels on or direct drive trainer. (esp downhill simulations, Uphill I think should be OK). So, what does the user expect to happen to their speed? (typical trainers don't drive the flywheel to increase speed). Hence what should be done? Wheels Stop, (-10% downhill for eg) but real world, the cyclist would still be coasting down.

1) wheels stop spinning but speed displayed will slowly ramp down to 0 or some number 2) wheels stop and speed = 0?

I personally prefer item 1 even tho the wheels has stopped. (and I also track indoor miles for chain wear/longevity and coast down would confuse the tracking - but no matter, riding outdoors has the same issue anyways). Hence simulated speed. Simulated speed is what @ericchristoffersen is doing I believe. (tho I'm confused as to his comment on removing it, but giving it more thought, I think he meant removing "virtual speed" and retaining "simulated speed" - correct?)

the link from @mattipee is interesting, in that (I believe) it's modelling the acceleration as well. (I paste the link again) https://physics.stackexchange.com/questions/226854/how-can-i-model-the-acceleration-velocity-of-a-bicycle-knowing-only-the-power-ou

Personally, since GC (and Fortius code is somewhat integrated into GC), I believe that the code should model the physics and present real-world scenarios when cycling in slope mode. That's just me.

ericchristoffersen commented 3 years ago

I am really not being understood. Maybe I'm using too much gc vernacular. "Virtual Speed" is not the same as "Simulated Speed". Mattipee is bringing up an entirely different discussion.

In GC virtual speed is a computed value stored on the realtimedata structure. At one point years ago it was used to compute virtual progress in gc. This is the expression that used to produce nan and inf, and doesn't now because it uses a proper cubic solver. The solver is irrelevant though because Virtual Speed does not model inertia. Virtual speed is a data field with a computed value in it. Its only use today is to be displayed. Because it is never 'right' I personally cannot envision when it is useful and people confuse it with other things, which is why I think it should be removed.

My only point here was that if you wish to model velocity while in erg mode, the virtual speed calculation is a bad choice because it will jump around a ton. It is convenient because it is stateless but it will suck. To get a realistic velocity you really need to model inertia.

switchabl commented 3 years ago

@ericchristoffersen Thanks for the clarification. I have to admit I was not really familiar with "Virtual Speed" in GC (I use GC for analysis but I have never used the Training module). I can't see much use for this as a data field either, particularly assuming that it is calculated using the normal power data.

I also agree that proper simulated speed (with inertia) should be used for erg mode. But anyone who wants to implement this in FortiusANT will probably need to look at refactoring the (somewhat messy) existing virtual speed code that is using the steady-state approach. So I think a slightly broader perspective is helpful. The points raised by @mjunkmyjunk are not directly related to the original proposal but it probably makes sense to address all this together.

We will definitely need to keep the steady-state virtual speed in FortiusANT for resistance control even if we add a proper simulated speed for display/recording.

WouterJD commented 3 years ago

Wauw, there is a lot of communication here.

The current implementation is:

Although with a GUI, FortiusAnt is a device-driver converting USB to ANT/BLE and vice versa; speed is displayed by the CTP.

WouterJD commented 3 years ago

Since there is no communication here, I assume can be closed. If not happy to reopen