Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bike team pursuit benchmark (rewriting 1153) #1387

Merged
merged 21 commits into from
Mar 21, 2022
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions nevergrad/benchmark/experiments.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from nevergrad.functions.games import game
from nevergrad.functions import iohprofiler
from nevergrad.functions import helpers
from nevergrad.functions.cycling import cycling
teytaud marked this conversation as resolved.
Show resolved Hide resolved
from .xpbase import Experiment as Experiment
from .xpbase import create_seed_generator
from .xpbase import registry as registry # noqa
Expand Down Expand Up @@ -1963,3 +1964,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]]
teytaud marked this conversation as resolved.
Show resolved Hide resolved
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
teytaud marked this conversation as resolved.
Show resolved Hide resolved
108 changes: 108 additions & 0 deletions nevergrad/functions/cycling/cycling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# 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: [email protected]
# 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):
teytaud marked this conversation as resolved.
Show resolved Hide resolved
"""
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