final submission

This commit is contained in:
2021-08-23 20:27:00 -05:00
parent 71fffe1cb6
commit dc0d34cb2e
3 changed files with 96 additions and 29 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.idea
.DS_Store

View File

@@ -25,12 +25,14 @@ class BikeDriveTrain:
List of integers representing tooth counts for cogs on rear cassette.
"""
#TODO Error handling for invalid inputs. Consider adding warnings for nonsensical gears/gear combinations
# IDEA Error handling for invalid inputs. Consider adding warnings for nonsensical gears/gear combinations
# Assign respective cogs to instance.
# ASSUMPTION: Gear sets won't need change after object creation, enforced as a read-only property.
self._front_cogs = front_cogs
self._rear_cogs = rear_cogs
# Let's also guarantee that the cogs are in order here, as they are in real life. Gear-sets are defined from
# largest to smallest.
self._front_cogs = sorted(front_cogs, reverse=True)
self._rear_cogs = sorted(rear_cogs, reverse=True)
# Calculate the ratios for this drive train.
# ASSUMPTION: Since front_cogs and rear_cogs won't change after instantiation, neither will the gear ratios. As
@@ -39,6 +41,7 @@ class BikeDriveTrain:
# ASSUMPTION: Practically, real bicycles will have a small set of gearing combinations, so this method is
# computationally trivial.
gear_combinations_and_ratios = []
for front_cog in front_cogs:
for rear_cog in rear_cogs:
gear_combinations_and_ratios.append((front_cog, rear_cog, front_cog / rear_cog))
@@ -75,26 +78,31 @@ class BikeDriveTrain:
Find the gear combination and its respective gear ratio that is nearest (but not over) target_ratio
Parameters
----------
target_ratio
target_ratio: float
A float representing the desired gear ratio.
Returns
-------
A tuple describing the gear combination and its respective gear ratio that is nearest (but not over)
tuple: description of the gear combination and its respective gear ratio that is nearest (but not over)
target_ratio in the form (front_cog, rear_cog, gear ratio).
"""
# first sort gear_combination_and_ratios from smallest to largest.
# first sort gear_combination_and_ratios from smallest to largest. Gear ratio is the third element of the
# gear_combination_and_ratio tuple
candidate_gear_combinations_and_ratios = sorted(self.gear_combinations_and_ratios,
key=lambda gear_combination_and_ratio:
gear_combination_and_ratio[2])
# then eliminate gear ratios that are greater than the target.
# #TODO error handling if all elements are eliminated
candidate_gear_combinations_and_ratios = [(front_cog, rear_cog, gear_ratio) for
(front_cog, rear_cog, gear_ratio) in
candidate_gear_combinations_and_ratios if gear_ratio < target_ratio]
candidate_gear_combinations_and_ratios if gear_ratio <= target_ratio]
# and the nearest without going over ratio is the last element of the candidate list
# Raise an value error if there are no candidates, meaning there is no gear ratio that is less than or equal
# to the target value.
if not candidate_gear_combinations_and_ratios:
raise ValueError(f"There is no gear ratio less than or equal to target gear ratio {target_ratio}")
# otherwise the nearest without going over ratio is the last element of the sorted candidate list.
target_gear_combination_and_ratio = candidate_gear_combinations_and_ratios[-1]
return target_gear_combination_and_ratio
@@ -110,9 +118,9 @@ class BikeDriveTrain:
Parameters
----------
target_ratio
target_ratio: float
A float representing the desired gear ratio.
initial_gear_combination
initial_gear_combination: Tuple[int, int]
The starting gear combination in the form of (front_gear, rear_gear) where front_gear and rear_gear are
integers describing the number of teeth in specified gear.
@@ -121,33 +129,82 @@ class BikeDriveTrain:
List of tuple of int, int, float: Steps in gear shifting sequence in the form
(front_cog, rear_cog, gear ratio)
"""
# Unpack the target gears and ratio
initial_front_gear, initial_rear_gear = initial_gear_combination
target_gear_combination_and_ratio = self.get_gear_combination(target_ratio)
# Make sure that the front initial gear is in the drivetrain.
if initial_front_gear not in self.front_cogs:
raise ValueError(f"Initial front gear '{initial_front_gear}' not in drivetrain.")
# TODO implement this method, using the rough steps below.
# first determine if it is a down-shift or an up-shift.
# Make sure that the rear initial gear is in the drivetrain.
if initial_rear_gear not in self.rear_cogs:
raise ValueError(f"Initial rear '{initial_rear_gear}' gear not in drivetrain.")
# Get the position of the initial gears
initial_front_gear_index = self.front_cogs.index(initial_front_gear)
initial_rear_gear_index = self.rear_cogs.index(initial_rear_gear)
# sort shifting steps (large cog first, then small cog) depending on whether it's a down-shift or up-shift.
# Unpack the target gears and ratio. get_gear_combination will either return a valid gear combination or an
# error, so there is no need for checks here.
target_front_gear, target_rear_gear, target_ratio = self.get_gear_combination(target_ratio)
# filter the list depending on target ratio, starting with the initial gear combination and stopping when the
# closest ratio to the target is achieved.
# Get the position of the target gears
target_front_gear_index = self.front_cogs.index(target_front_gear)
target_rear_gear_index = self.rear_cogs.index(target_rear_gear)
# return list
# Determine if a down-shift or an up-shift is required.
if initial_front_gear > target_front_gear: # Requires an downshift
# Filter gears starting at initial and ending at target. target_index + 1 is necessary to ensure inclusion
# of the target in the slice.
front_shift_sequence = self.front_cogs[initial_front_gear_index:target_front_gear_index+1]
elif initial_front_gear < target_front_gear: # Requires a up-shift
# Since the gears are ordered largest to smallest by definition, for an up-shift we will need to start with
# target index, end with initial and then flip the list to filter gears starting at target and then reverse
# the list.
# TODO this is a little convoluted, and there is probably a more legible way to do this slicing.
front_shift_sequence = self.front_cogs[target_front_gear_index:initial_front_gear_index + 1]
front_shift_sequence.reverse()
else: # No shift required
front_shift_sequence = [initial_front_gear]
# TODO In the meantime, this function will return a "not implemented" error.
if initial_rear_gear > target_rear_gear: # Requires an down-shift.
# Filter gears starting at initial and ending at target. target_index + 1 is necessary to ensure inclusion
# of the target in the slice.
rear_shift_sequence = self.rear_cogs[initial_rear_gear_index:target_rear_gear_index+1]
elif initial_rear_gear < target_rear_gear: # Requires a up-shift, so flip the order of the shift
# Since the gears are ordered largest to smallest by definition, for an up-shift we will need to start with
# target index, end with initial and then flip the list to filter gears starting at target and then reverse
# the list.
# TODO this is a little convoluted, and there is probably a more legible way to do this slicing.
rear_shift_sequence = self.rear_cogs[target_rear_gear_index:initial_rear_gear_index + 1]
rear_shift_sequence.reverse()
else: # No shift required
rear_shift_sequence = [initial_rear_gear]
return self.gear_combinations_and_ratios
shift_sequence = []
# Shift front gear first.
for front_shift in front_shift_sequence:
shift_sequence.append((front_shift, initial_rear_gear, front_shift/initial_rear_gear))
# Then shift rear gear
# This method necessarily starts with the initial rear gear, so we should skip the first element
# of the rear_shift_sequence, since it starts with the initial rear gear.
for rear_shift in rear_shift_sequence[1:]:
shift_sequence.append((target_front_gear, rear_shift, target_front_gear/rear_shift))
return shift_sequence
def produce_formatted_shift_sequence(self, target_ratio: float, initial_gear_combination: Tuple[int, int]):
"""
A method to produce a formatted shift sequence for a given target ratio and initial gear combination.
Prints in the format f"{step} - F:{front_gear} R:{rear_gear} Ratio {ratio}"
Parameters
----------
target_ratio
target_ratio: float
A float representing the desired gear ratio.
initial_gear_combination
initial_gear_combination: Tuple[int, int]
The starting gear combination in the form of (front_gear, rear_gear) where front_gear and rear_gear are
integers describing the number of teeth in specified gear.
Returns
@@ -159,5 +216,7 @@ class BikeDriveTrain:
sequence = self.get_shift_sequence(target_ratio, initial_gear_combination)
# print the sequence using string formatter
for i, (front_gear, rear_gear, ratio) in enumerate(sequence):
print(f"{i}: F:{front_gear}, R:{rear_gear}, {ratio:3f}")
for step, (front_gear, rear_gear, ratio) in enumerate(sequence, start=1):
print(f"{step} - F:{front_gear} R:{rear_gear} Ratio {ratio:.3f}")
return None

View File

@@ -10,12 +10,10 @@ def drive_train():
"""
return BikeDriveTrain([38, 30], [28, 23, 19, 16])
def test_bike_drive_train(drive_train):
assert drive_train.front_cogs == [38, 30]
assert drive_train.rear_cogs == [28, 23, 19, 16]
def test_bike_drive_train_ratios(drive_train):
# generate list of the gear ratios for the given front crank and rear cassette
ratios = [(38, 28, 38 / 28),
@@ -60,9 +58,17 @@ def test_bike_drive_train_shift_seq(drive_train):
(30, 19, 30 / 19)
]
# Test case an downshifting sequence
sequence = drive_train.get_shift_sequence(target_ratio=1.4, initial_gear_combination=[30, 19])
assert sequence == [
(30, 19, 30 / 19),
(38, 19, 38 / 19),
(38, 23, 38 / 23),
(38, 28, 38 / 28),
]
def test_bike_drive_train_shift_seq_output(drive_train, capsys):
drive_train.get_shift_sequence(target_ratio=1.6, initial_gear_combination=[38, 28])
drive_train.produce_formatted_shift_sequence(target_ratio=1.6, initial_gear_combination=[38, 28])
out, err = capsys.readouterr()
assert out == "1 - F:38 R:28 Ratio 1.357\n2 - F:30 R:28 Ratio 1.071\n3 - F:30 R:23 Ratio 1.304\n4 - F:30 R:19 Ratio 1.579"
assert out == "1 - F:38 R:28 Ratio 1.357\n2 - F:30 R:28 Ratio 1.071\n3 - F:30 R:23 Ratio 1.304\n4 - F:30 R:19 Ratio 1.579\n"