diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 3fad9778..816eceee 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1101,7 +1101,8 @@ def interactive_agegroup_plot(df): return script,div def interactive_cpchart(rower,thedistances,thesecs,theavpower, - theworkouts,promember=0): + theworkouts,promember=0, + wcpower=[],wcdurations=[]): message = 0 # plot tools @@ -1167,11 +1168,23 @@ def interactive_cpchart(rower,thedistances,thesecs,theavpower, ) - # fitting the data to three parameter CP model fitfunc = lambda pars,x: pars[0]/(1+(x/pars[2])) + pars[1]/(1+(x/pars[3])) errfunc = lambda pars,x,y: fitfunc(pars,x)-y - + p0 = [500,350,10,8000] + wcpower = pd.Series(wcpower) + wcdurations = pd.Series(wcdurations) + + # fitting WC data to three parameter CP model + if len(wcdurations)>=4: + p1wc, success = optimize.leastsq(errfunc, p0[:], + args = (wcdurations,wcpower)) + else: + p1wc = None + + # fitting the data to three parameter CP model + + p1 = p0 if len(thesecs)>=4: @@ -1184,6 +1197,21 @@ def interactive_cpchart(rower,thedistances,thesecs,theavpower, fitt = pd.Series(10**(4*np.arange(100)/100.)) fitpower = fitfunc(p1,fitt) + if p1wc is not None: + fitpowerwc = 0.95*fitfunc(p1wc,fitt) + fitpowerexcellent = 0.7*fitfunc(p1wc,fitt) + fitpowergood = 0.6*fitfunc(p1wc,fitt) + fitpowerfair = 0.5*fitfunc(p1wc,fitt) + fitpoweraverage = 0.4*fitfunc(p1wc,fitt) + + else: + fitpowerwc = 0*fitpower + fitpowerexcellent = 0*fitpower + fitpowergood = 0*fitpower + fitpowerfair = 0*fitpower + fitpoweraverage = 0*fitpower + + message = "" if len(fitpower[fitpower<0]) > 0: @@ -1203,6 +1231,11 @@ def interactive_cpchart(rower,thedistances,thesecs,theavpower, ), spm = 0*fitpower, power = fitpower, + fitpowerwc = fitpowerwc, + fitpowerexcellent = fitpowerexcellent, + fitpowergood = fitpowergood, + fitpowerfair = fitpowerfair, + fitpoweraverage = fitpoweraverage, fpace = nicepaceformat(fitp2), ) ) @@ -1281,6 +1314,25 @@ def interactive_cpchart(rower,thedistances,thesecs,theavpower, plot.line('duration','power',source=sourcepaul,legend="Paul's Law") plot.line('duration','power',source=sourcecomplex,legend="CP Model", color='green') + plot.line('duration','fitpowerwc',source=sourcecomplex, + legend="World Class", + color='Maroon',line_dash='dotted') + + plot.line('duration','fitpowerexcellent',source=sourcecomplex, + legend="Excellent", + color='Purple',line_dash='dotted') + + plot.line('duration','fitpowergood',source=sourcecomplex, + legend="Good", + color='Olive',line_dash='dotted') + + plot.line('duration','fitpowerfair',source=sourcecomplex, + legend="Fair", + color='Gray',line_dash='dotted') + + plot.line('duration','fitpoweraverage',source=sourcecomplex, + legend="Average", + color='SkyBlue',line_dash='dotted') script, div = components(plot) diff --git a/rowers/metrics.py b/rowers/metrics.py index e4efe26f..fbd6041e 100644 --- a/rowers/metrics.py +++ b/rowers/metrics.py @@ -319,12 +319,26 @@ def calc_trimp(df,sex,hrmax,hrmin): return trimp -def getagegroup2k(age,sex='male',weightcategory='hwt'): - df = pd.DataFrame( - list( - C2WorldClassAgePerformance.objects.filter( - sex=sex, - weightcategory=weightcategory +def getagegrouprecord(age,sex='male',weightcategory='hwt', + distance=2000,duration=None): + if not duration: + df = pd.DataFrame( + list( + C2WorldClassAgePerformance.objects.filter( + distance=distance, + sex=sex, + weightcategory=weightcategory + ).values() + ) + ) + else: + duration=60*int(duration) + df = pd.DataFrame( + list( + C2WorldClassAgePerformance.objects.filter( + duration=duration, + sex=sex, + weightcategory=weightcategory ).values() ) ) diff --git a/rowers/templates/.#rankings.html b/rowers/templates/.#rankings.html new file mode 100644 index 00000000..8f484e75 --- /dev/null +++ b/rowers/templates/.#rankings.html @@ -0,0 +1 @@ +E408191@CZ27LT9RCGN72.12348:1512983261 \ No newline at end of file diff --git a/rowers/templates/rankings.html b/rowers/templates/rankings.html index ecaa84e2..8e82db94 100644 --- a/rowers/templates/rankings.html +++ b/rowers/templates/rankings.html @@ -160,6 +160,15 @@ {{ the_div|safe }} +
The dashed lines are based on the Concept2 rankings for your age, gender + and weight category. World class means within 5% of World Record in terms + of power. Excellent, Good, and Fair indicate the power levels of the top + 10%, 25% and 50% of the Concept2 rankings. Average is taken + as being in the top 75%, given that the Concept2 rankings probably + represent the more competitive sub-group of all people who erg. + Please note that this is a prediction for people of exactly your age, +and your actual place in the Concept2 Ranking may be different.
+No ranking pieces found.
' + paulslope = 1 + paulintercept = 1 + p1 = [1,1,1,1] + message = "" + + + if request.method == 'POST' and "piece" in request.POST: + form = PredictedPieceForm(request.POST) + if form.is_valid(): + value = form.cleaned_data['value'] + hourvalue,value = divmod(value,60) + if hourvalue >= 24: + hourvalue = 23 + pieceunit = form.cleaned_data['pieceunit'] + if pieceunit == 'd': + rankingdistances.append(value) + else: + rankingdurations.append(datetime.time(minute=int(value),hour=int(hourvalue))) + else: + form = PredictedPieceForm() + + rankingdistances.sort() + rankingdurations.sort() + + + predictions = [] + cpredictions = [] + + + for rankingdistance in rankingdistances: + # Paul's model + p = paulslope*np.log10(rankingdistance)+paulintercept + velo = 500./p + t = rankingdistance/velo + pwr = 2.8*(velo**3) + a = {'distance':rankingdistance, + 'duration':timedeltaconv(t), + 'pace':timedeltaconv(p), + 'power':int(pwr)} + predictions.append(a) + + # CP model - + pwr2 = p1[0]/(1+t/p1[2]) + pwr2 += p1[1]/(1+t/p1[3]) + + if pwr2 <= 0: + pwr2 = 50. + + velo2 = (pwr2/2.8)**(1./3.) + + if np.isnan(velo2) or velo2 <= 0: + velo2 = 1.0 + + t2 = rankingdistance/velo2 + + pwr3 = p1[0]/(1+t2/p1[2]) + pwr3 += p1[1]/(1+t2/p1[3]) + + if pwr3 <= 0: + pwr3 = 50. + + velo3 = (pwr3/2.8)**(1./3.) + if np.isnan(velo3) or velo3 <= 0: + velo3 = 1.0 + + t3 = rankingdistance/velo3 + p3 = 500./velo3 + + a = {'distance':rankingdistance, + 'duration':timedeltaconv(t3), + 'pace':timedeltaconv(p3), + 'power':int(pwr3)} + cpredictions.append(a) + + + + + for rankingduration in rankingdurations: + t = 3600.*rankingduration.hour + t += 60.*rankingduration.minute + t += rankingduration.second + t += rankingduration.microsecond/1.e6 + + # Paul's model + ratio = paulintercept/paulslope + + u = ((2**(2+ratio))*(5.**(3+ratio))*t*np.log(10))/paulslope + + d = 500*t*np.log(10.) + d = d/(paulslope*lambertw(u)) + d = d.real + + velo = d/t + p = 500./velo + pwr = 2.8*(velo**3) + a = {'distance':int(d), + 'duration':timedeltaconv(t), + 'pace':timedeltaconv(p), + 'power':int(pwr)} + predictions.append(a) + + # CP model + pwr = p1[0]/(1+t/p1[2]) + pwr += p1[1]/(1+t/p1[3]) + + if pwr <= 0: + pwr = 50. + + velo = (pwr/2.8)**(1./3.) + + if np.isnan(velo) or velo <=0: + velo = 1.0 + + d = t*velo + p = 500./velo + a = {'distance':int(d), + 'duration':timedeltaconv(t), + 'pace':timedeltaconv(p), + 'power':int(pwr)} + cpredictions.append(a) + + + messages.error(request,message) + return render(request, 'rankings.html', + {'rankingworkouts':theworkouts, + 'interactiveplot':script, + 'the_div':div, + 'predictions':predictions, + 'cpredictions':cpredictions, + 'nrdata':len(thedistances), + 'form':form, + 'dateform':dateform, + 'deltaform':deltaform, + 'id': theuser, + 'theuser':uu, + 'startdate':startdate, + 'enddate':enddate, + 'teams':get_my_teams(request.user), + }) + @user_passes_test(ispromember,login_url="/",redirect_field_name=None) def workout_update_cp_view(request,id=0): try: