Backflip
🎯 Goal
Welcome to the last challenge of this series. The final task is to do a backflip and it is quite a big difficulty jump from the previous ones.
It is important to conceptualize what is needed before starting. First, jump height is crucial which will give you time in the air to complete the rotation. But a jump that considers only height will not be sufficient. You also need to generate angular momentum to rotate the body backwards. This is a tricky balance with this particular model since it has no arms to help generate that momentum which leaves most of the work to the legs.
If you manage to take off with enough vertical velocity and angular momentum, your only consideration now is to make your moment of inertia as small as possible to rotate faster. This means tucking in the legs and keeping the body compact during the flight phase. Don't worry about the landing for now. But if you somehow manage it, let me know!
Good luck!
📋 Technical Details
This model is around four times stronger than the previous ones to compensate for the lack of arms and the increased power needed to perform a backflip. If you input too high muscle excitations especially in the trunk muscles without co-contraction, the model may get in to configurations where it is difficult for the solver to calculate states. This will cause increased simulation times and may result in timeouts. So try to be a bit more mindful of the inputs this time around.
Since you will be encountering increased angles as well there may be issues with gimbal locking in certain configurations. This will be fixed in the future versions.
Scoring
In this challenge your score is only based on the amount of rotation you can complete before landing. Your total rotation is calculated by the difference of your pelvis orientation at the time of your take-off and landing. So you can't just fall backwards and expect to get high points! Your point is also slashed in half for excessive lumbar extension.
👨💻 Your Solution
📊 Results
🔒 Optimal Solution (Click to Reveal)
📊 Performance Comparison
👨💻 Your Results
🏆 Solver Results
📈 Your Performance
🧬 How Evolution Found the Answer (or couldn't?)
As you can see from the final results, it wasn't particularly successful in performing a backflip. Despite the increased strength, the model couldn't generate enough angular momentum and height to complete a full rotation. However, I would say the technique it developed looks on point, so I can shift the blame to the insufficient strength of the model rather than the algorithm.
Due to the higher complexity of this movement, tuning the fitness function required a lot more explicit scoring system. For example the algorithm wasn't able to figure out that it needed to "tuck" during the flight phase to reduce moment of inertia and rotate faster. So I had to give points for knee flexion and hip flexion separately. In addition, I added some penalties for excessive joint angles. Since some strategies achieved very good rotations but weren't realistic.
Here is an overview of the fitness function:
🎯 Fitness Components
The fitness function uses a weighted scoring system with four main components:
- Rotation Score (weight: 1.0) - Rewards backward rotation of the body
- Jump Height (weight: 0.75) - Encourages maximum vertical displacement
- Knee Flexion (weight: 0.5) - Rewards tucking the knees during flight
- Hip Flexion (weight: 0.5) - Rewards bringing the legs toward the torso
Additionally, several penalty multipliers (×0.5) are applied for unrealistic ranges of motion:
- Excessive hip hyperextension (< -30°)
- Excessive lumbar extension (> 40°)
- Excessive lumbar flexion (< -80°)
And below is the complete code snippet for the fitness function:
View the "Backflip Fitness Function"
def get_fitness(self, gen_counter, json_data=None):
if not json_data:
return 0
def get_airborn_idx(calc_pos):
take_off_threshold = 0.05 # 5 cm threshold to start giving points for being airborn
start_pos = calc_pos[0]
calc_pos = np.array(calc_pos)
airborn_idx = np.where(calc_pos - start_pos > take_off_threshold)[0]
if len(airborn_idx) == 0:
return -1, -1 # Never airborn
to_idx = airborn_idx[0] # Take-off index
td_idx = np.where(calc_pos[to_idx:] - start_pos <= take_off_threshold)[0] # Find touchdown after take-off
if len(td_idx) == 0:
td_idx = -1 # Never touches down again
else:
td_idx = td_idx[0] + to_idx
return to_idx, td_idx
def calculate_knee_flexion_score():
if to_idx == -1:
return 0 # No airborn phase detected
air_kf = knee_flexion[to_idx:td_idx] #Knee flexion during airborn phase
min_air_kf = np.min(air_kf)
target_knee_flexion = 150 # Target knee flexion angle for a good backflip
knee_flexion_score = min(1, -min_air_kf/target_knee_flexion)
return knee_flexion_score
def calculate_hip_flexion_score():
if to_idx == -1:
return 0 # No airborn phase detected
air_hf = hip_flexion[to_idx:td_idx] #Hip flexion during airborn phase
max_air_hf = np.max(air_hf)
target_hip_flexion = 150 # Target hip flexion angle for a good backflip
hip_flexion_score = min(1, max_air_hf/target_hip_flexion)
return hip_flexion_score
def calculate_rotation_score():
if to_idx == -1:
return 0 # No airborn phase detected
pelvis_rotation = pelvis_tilt[to_idx:td_idx]
total_rotation = pelvis_rotation[-1] - pelvis_rotation[0] #total rotation during airborn phase
target_rotation = 400 # Target rotation for a full backflip
rotation_score = min(1.0, total_rotation / target_rotation)
return rotation_score
def calculate_height_score():
max_height = np.max(com_y)
starting_height = com_y[0]
jump_height = max_height - starting_height
target_height = 0.70 # Expected height for a good backflip
height_score = min(1.0, jump_height / target_height)
return height_score
# Extracting the relevant data from the json
com_y = json_data['data']['center_of_mass_Y'] #Center of mass vertical position
pelvis_tilt = np.rad2deg(json_data['joint_angles']['data']['pelvis_tilt']) #Pelvis medio-lateral orientation
knee_flexion = np.rad2deg(json_data['joint_angles']['data']['knee_angle_r'])
hip_flexion = np.rad2deg(json_data['joint_angles']['data']['hip_flexion_r'])
lumbar_extension = np.rad2deg(json_data['joint_angles']['data']['lumbar_extension'])
calc_pos = json_data['data']['calcn_r_Y'] # Right heel vertical position, to determine flight phase
to_idx, td_idx = get_airborn_idx(calc_pos) # Get the phase where the model is in flight
## Get the scores
scores = {
'rotation': calculate_rotation_score(),
'jump_height': calculate_height_score(),
'knee_flexion': calculate_knee_flexion_score(),
'hip_flexion': calculate_hip_flexion_score(),
}
weights = {
'rotation': 1.0,
'jump_height': 0.75,
'knee_flexion': 0.5,
'hip_flexion': 0.5,
}
self.fitness = sum((weights[k] * scores[k] for k in scores))
if np.min(hip_flexion) < -30:
self.fitness *= 0.5 # Penalize extreme hip hyperextension
if np.max(lumbar_extension) > 40:
self.fitness *= 0.5 # Penalize excessive lumbar extension
if np.min(lumbar_extension) < -80:
self.fitness *= 0.5 # Penalize excessive lumbar flexion
return self.fitness if self.fitness > 0 else 0 # Ensure non-negative fitness
Overall I'm happy with how the solution turned out eventhough it couldn't fully achieve the backflip.
Thanks for following along this far and let me know if you have any ideas for future challenges or any feedback!
Here is a link to the github repository of this project where you can find the full source code: GitHub Repository