Skip to content

Commit

Permalink
Add bike team pursuit benchmark (rewriting 1153) (#1387)
Browse files Browse the repository at this point in the history
* New problem function: Team Pursuit Track Cycling

* submitting changes for team pursuit cycling problem

* adding cycling experiment

* fixing linting issues

* headers

* fix

* fix

* fix

* fix

* Update nevergrad/benchmark/experiments.py

Co-authored-by: Jérémy Rapin <jrapin.github@gmail.com>

* testcycling

* Bike2 (#1390)

* testcycling

* fix

* fix

* Update nevergrad/benchmark/experiments.py

Co-authored-by: Jérémy Rapin <jrapin.github@gmail.com>

* Update nevergrad/benchmark/experiments.py

Co-authored-by: Jérémy Rapin <jrapin.github@gmail.com>

* Update nevergrad/functions/cycling/cycling.py

Co-authored-by: Jérémy Rapin <jrapin.github@gmail.com>

* Update nevergrad/functions/cycling/__init__.py

Co-authored-by: Jérémy Rapin <jrapin.github@gmail.com>

* fix

Co-authored-by: Ryan Kroon <rkroon19@gmail.com>
Co-authored-by: Jérémy Rapin <jrapin.github@gmail.com>
3 people authored Mar 21, 2022

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent cede21a commit 07f6ec0
Showing 11 changed files with 681 additions and 0 deletions.
14 changes: 14 additions & 0 deletions nevergrad/benchmark/experiments.py
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@
from nevergrad.functions.games import game
from nevergrad.functions import iohprofiler
from nevergrad.functions import helpers
from nevergrad.functions.cycling import Cycling
from .xpbase import Experiment as Experiment
from .xpbase import create_seed_generator
from .xpbase import registry as registry # noqa
@@ -1978,3 +1979,16 @@ def unit_commitment(seed: tp.Optional[int] = None) -> tp.Iterator[Experiment]:
xp = Experiment(func, algo, budget, num_workers=1, seed=next(seedg))
if not xp.is_incoherent:
yield xp


def team_cycling(seed: tp.Optional[int] = None) -> tp.Iterator[Experiment]:
"""Experiment to optimise team pursuit track cycling problem."""
seedg = create_seed_generator(seed)
optims = ["NGOpt10", "CMA", "DE"]
funcs = [Cycling(num) for num in [30, 31, 61, 22, 23, 45]]
for function in funcs:
for budget in [3000]:
for optim in optims:
xp = Experiment(function, optim, budget=budget, num_workers=10, seed=next(seedg))
if not xp.is_incoherent:
yield xp
Binary file not shown.
Binary file not shown.
6 changes: 6 additions & 0 deletions nevergrad/functions/cycling/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

from .cycling import Cycling as Cycling
109 changes: 109 additions & 0 deletions nevergrad/functions/cycling/cycling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

# Creator: Ryan Kroon, Email: rkroon19@gmail.com
# Accompanying paper: Extending Nevergrad, an Optimisation Platform (in directory)

# Evolving Pacing Strategies for Team Pursuit Track Cycling
# Markus Wagner, Jareth Day, Diora Jordan, Trent Kroeger, Frank Neumann
# Advances in Metaheuristics, Vol. 53, Springer, 2013.
# https://link.springer.com/chapter/10.1007/978-1-4614-6322-1_4
# Java code: https://cs.adelaide.edu.au/~markus/pub/TeamPursuit.zip

import numpy as np
import typing as tp
from .. import base
from nevergrad.parametrization import parameter as p
from .womensteampursuit import womensteampursuit
from .mensteampursuit import mensteampursuit


class Cycling(base.ExperimentFunction):

"""
Team Pursuit Track Cycling Simulator.
Parameters
----------
strategy: int
Refers to Transition strategy or Pacing strategy (or both) of the cyclists; this depends on the strategy length.
Strategy length can only be 30, 31, 61, 22, 23, 45.
30: mens transition strategy.
31: mens pacing strategy.
61: mens transition and pacing strategy combined.
22: womens transition strategy.
23: womens pacing strategy.
45: womens transition and pacing strategy combined.
"""

def __init__(self, strategy_index: int = 30) -> None:

# Preliminary stuff.
women = strategy_index in [22, 23, 45]
param_transition = p.TransitionChoice([False, True], repetitions=22 if women else 30)
init = (400 if women else 550) * np.ones(23 if women else 31)
gender = "Women" if women else "Men"
param_pacing = p.Array(init=init, lower=200, upper=1200)
target_function = team_pursuit_simulation

# optimising transition strategy
if strategy_index in (22, 30):
parameter: tp.Any = param_transition
parameter.set_name(f"{gender} Transition strategy")
parameter.set_name("")

# optimising pacing strategy
elif strategy_index in (23, 31):
init = 550 * np.ones(strategy_index)
parameter = param_pacing
parameter.set_name(f"{gender} Pacing strategy")
parameter.set_name("")

# optimising pacing and transition strategies
elif strategy_index in (45, 61):
init = 0.5 * np.ones(strategy_index) # type: ignore
parameter = p.Instrumentation(transition=param_transition, pacing=param_pacing)
parameter.set_name(f"{gender} Pacing and Transition")
# For some reason the name above does not work...
# It generates a very long name like
# "(Wom|M)ens Pacing and Transition:[0.5,...
parameter.set_name("")

# error raised if invalid strategy length given
else:
raise ValueError("Strategy length must be any of 22, 23, 30, 31, 45, 61")
super().__init__(target_function, parameter)
# assert len(self.parametrization.sample().value) == strategy_index, f"{len(self.parametrization.sample().value)} != {strategy_index} (init={init} with len={len(init)})."


def team_pursuit_simulation(x) -> float:

if len(x) == 2: # Let us concatenate the instrumentation.
pacing = x[1]["pacing"]
transition = x[1]["transition"]
elif len(x) in (30, 22):
transition = x
pacing = [550 if len(x) == 30 else 400] * (len(x) + 1)
elif len(x) in (31, 23):
pacing = x
transition = [True, False] * ((len(x) - 1) // 2)
else:
raise ValueError(f"len(x) == {len(x)}")

# What is this ?
# for i in range(0, len(pacing_strategy)):
# pacing_strategy[i] = 100 * pacing_strategy[i] + 200

# Create a mensteampursuit oor womensteampursuit object.
team_pursuit: tp.Any = womensteampursuit() if len(pacing) == 23 else mensteampursuit()
assert len(pacing) in (23, 31)

# Simulate event with the default strategies
result = team_pursuit.simulate(transition, pacing)

if result.get_finish_time() > 10000: # in case of inf
return 10000
else:
return float(result.get_finish_time())
153 changes: 153 additions & 0 deletions nevergrad/functions/cycling/cyclist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

from .teampursuit import teampursuit
import math


class Cyclist:

# static class variables
max_power = 1200
min_power = 200
drag_coeffiecient = 0.65
mechanical_efficiency = 0.977
bike_mass = 7.7
fatigue_level = 0

def __init__(self, height, weight, mean_maximum_power, event, start_position, gender):

self.height = height
self.weight = weight
self.mean_maximum_power = mean_maximum_power
self.event = event
self.start_position = start_position
self.position = start_position
self.gender = gender
self.current_velocity = 0.0
self.fatigue_level = 0

self.update_cda()
self.update_total_energy()
self.remaining_energy = self.total_energy

def set_pace(self, power):
fatigue_factor = 1 - (0.01 * self.fatigue_level)

delta_ke = (
(power * self.mechanical_efficiency * fatigue_factor)
- (self.coefficient_drag_area * 0.5 * self.event.air_density * math.pow(self.current_velocity, 3))
- (
teampursuit.friction_coefficient
* (self.weight + self.bike_mass)
* teampursuit.gravitational_acceleration
* self.current_velocity
)
) * teampursuit.time_step

new_velocity = math.pow(
((2 * delta_ke / (self.weight + self.bike_mass)) + math.pow(self.current_velocity, 2)), 0.5
)
acceleration = new_velocity - self.current_velocity
distance = (self.current_velocity * teampursuit.time_step) + (
0.5 * acceleration * math.pow(teampursuit.time_step, 2)
)

self.current_velocity = new_velocity

if self.remaining_energy > power * teampursuit.time_step:
self.remaining_energy -= power * teampursuit.time_step
else:
self.remaining_energy = 0.0

return distance

def follow(self, distance):
fatigue_factor = 1 - (0.01 * self.fatigue_level)

acceleration = (
2
* (distance - (self.current_velocity * teampursuit.time_step))
/ math.pow(teampursuit.time_step, 2)
)
new_velocity = self.current_velocity + (acceleration * teampursuit.time_step)
delta_ke = 0.5 * (self.weight + self.bike_mass) * (new_velocity - self.current_velocity)
power = (
(
self.coefficient_drag_area
* teampursuit.drafting_coefficients[self.position - 2]
* 0.5
* self.event.air_density
* math.pow(self.current_velocity, 3)
)
+ (
teampursuit.friction_coefficient
* (self.weight + self.bike_mass)
* teampursuit.gravitational_acceleration
* self.current_velocity
)
+ (delta_ke / teampursuit.time_step)
) / (self.mechanical_efficiency * fatigue_factor)

self.current_velocity = new_velocity

if self.remaining_energy > power * teampursuit.time_step:
self.remaining_energy -= power * teampursuit.time_step
else:
self.remaining_energy = 0.0

def get_height(self):
return self.height

def get_weight(self):
return self.weight

def get_mean_maximum_power(self):
return self.mean_maximum_power

def get_remaining_energy(self):
return self.remaining_energy

def get_position(self):
return self.position

def set_weight(self, weight):
self.weight = weight
self.update_cda()
self.update_total_energy()

def set_height(self, height):
self.height = height
self.update_cda()

def set_mean_maximum_power(self, mean_maximum_power):
self.mean_maximum_power = mean_maximum_power
self.update_total_energy()

def set_position(self, position):
self.position = position

def increase_fatigue(self):
self.fatigue_level += 2

def recover(self):
if self.fatigue_level > 0:
self.fatigue_level -= 1

def reset(self):
self.remaining_energy = self.total_energy
self.position = self.start_position
self.fatigue_level = 0
self.current_velocity = 0

def update_cda(self):
self.coefficient_drag_area = self.drag_coeffiecient * (
(0.0293 * math.pow(self.height, 0.725)) * (math.pow(self.weight, 0.425)) + 0.0604
)

def update_total_energy(self):
coeff = 240 if self.gender == "male" else 210

self.total_energy = self.mean_maximum_power * self.weight * coeff
Loading

0 comments on commit 07f6ec0

Please sign in to comment.