Skip to content

Commit 27921c5

Browse files
rmburgknoellle
andauthored
Mpc step planning (#1854)
* mpc step planning * taplo fmt * Nuke `traits::LossField` * Use parameters instead of hard-coded constants * Move test around * Organize imports * Add todo comment * undo changes in golden goal * Rename `AlignWithPath` -> `Unspecified` to reflect new semantics * Refactor: remove `StepPlanningLossField` * Clean up old comments * Fix failing test * Borrow `Path` * Borrow `Parameters` * Naming consistency * Remove unused variable * Remove another unused variable * Add todo comment * Remove outdated comment * Re-implement customizable walk volume exponents * Remove unused parameters * Fix parameter path * Undo accidental change * Remove todo comment * Remove outdated test comment * Update todo comment * Reduce backwards step length * Verify path_distance gradient * Add some new gradient types * Pose: use `Angle` for orientation * Add more gradient verification tests * Use test_path fn from test_utils module * Refactor module layout * Update behavior sim scenarios * Fix bugs in target orientation field * Make learning rate configurable * Expose current support side and draw soles in twix * Expose step plan cost * Also calculate greedy step plan * Remove pub use * Use correct norm for normalizing gradient * Fix greedy shit * Remove `PoseAndSupportFoot` struct * Rename variable * Simulate support foot switch in greedy step plan * Remove `dbg` * Fix incorrect fake gradient size * --wip-- * Change mpc test scenario to use fixed path * --wip-- * Use OpEn/PANOC solver (WIP!) * Fix step drawing * Improve step plan drawing * Improve mpc demo scenario path * Normalize gradient * Tweak parameters * Tweak demo scenario path * Fix incorrect step plan drawing (again) * Remove unused parameter * Remove transparency of planned steps * Paint 0th step * Update optimization parameters * Remove `PlannedStep` * Remove unused parameters * Add benchmark * Optimization: precompute cost function parts * Remove unused field * Remove unused field * Tiny refactor * Add `EndPoints` trait * Refactor: reuse length impl * Tiny refactor * impl endpoints for path * Take direct step to target if possible * Fix occasional instability * Organize step planning cost factors * Don't panic if solver fails * Fix trailing comma * Remove unused import * Refactor alignment importance scaling * Verify walk orientation gradient * Remove hybrid alignment * Recycle variables of previous cycle * Forget previous step plan on support side switch * Update step planning test scenario * Enable walk and target orientation * Tweak parameters to fix donking * Add warm start parameter * Refactor * Greedy step plan: Clamp to anatomical constraints * Fix missing struct field * Twix: draw target orientation * Refactor * Re-add `OrientationMode::AlignWithPath` * Change some magic numbers * Move `WalkVolumeExtents` out of `StepPlanningOptimizationParameters` * Use constant for number of variables per step * Move `gradient_type` module to its own file * Rework step planner parameters and step clamping * Remove duplicate gradient_type module * Add `PoseGradient::zeros()` convenience method * Remove unneccessary check * Fix sign flip in path alignment * Add proptest for `OrientationMode::AlignWithPath` * Use `OrientationMode::AlignWithPath` in `walk_to_pose` * Remove unneccessary `abs()` * Reduce path alignment tolerance parameter * Add tolerance field to `OrientationMode` * Add orientation mode parameter to `walk_to_pose` and force kickoff striker to look towards the other field half * Fix searching behavior * Tweak target orientation in lost_ball * Add todo comment * Move `StepPlanning` out of `step_plan.rs` * Re-add PR #1914, which was removed in a rebase this was easier than correctly rebasing. * Use `Path` newtype * Fix another timing issue * Move step planning proptest function bodies out of macro * Reject proptest samples on test path discontinuities * Add proptest config to increase number of samples * Add parameter to disable MPC step planning * Constify number of steps * Remove todo * Remove `max_step_size` parameter * Add `Orientation` type * Consolidate `WrapGradient` and `UnwrapGradient` * Move test_path out of cfg(test) gate Otherwise, it could not be used across crate boundaries and in external benches/tests. * penalize orientation towards the wrong side of the path * Pin OpEn dependency * Remove step_planning_duration * Clamp inside movement without anatomical constraints * Rename `plan_step` to make intent clearer * Adress review comments * Remove unnecessary special case for striker walk-in * Remove unused utilities * Move test_utils module to its own file * Organize imports * Refactor: use hybrid alignment distance again * Re-implement hybrid alignment for greedy step plan * Refactor direct step and fix a few issues Before, direct step was not working for the greedy step planner * Remove leftover comment * Naming consistency * Use methods of `Path` type --------- Co-authored-by: Konrad Nölle <konrad@familie-noelle.de>
1 parent 6c1ed64 commit 27921c5

File tree

83 files changed

+4778
-550
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+4778
-550
lines changed

Cargo.lock

Lines changed: 317 additions & 31 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ members = [
4343
"crates/source_analyzer",
4444
"crates/spl_network",
4545
"crates/spl_network_messages",
46+
"crates/step_planning",
47+
"crates/step_planning_solver",
4648
"crates/types",
4749
"crates/vision",
4850
"crates/walking_engine",
@@ -164,11 +166,13 @@ ndarray-conv = "0.4.1"
164166
nix = { version = "0.29", features = ["ioctl"] }
165167
nucleo-matcher = "0.3.1"
166168
num-derive = "0.4.2"
169+
num-dual = "0.11.1"
167170
num-traits = "0.2"
168171
object_detection = { path = "crates/object_detection" }
169172
once_cell = "1.20.3"
170173
openvino = { version = "0.8.0", features = ["runtime-linking"] }
171174
opn = { path = "crates/opn" }
175+
optimization_engine = "0.9.1"
172176
opusfile-ng = "0.1.0"
173177
ordered-float = "4.6.0"
174178
parameters = { path = "crates/parameters" }
@@ -214,6 +218,8 @@ source_analyzer = { path = "crates/source_analyzer" }
214218
spl_network = { path = "crates/spl_network" }
215219
spl_network_messages = { path = "crates/spl_network_messages" }
216220
splines = { version = "=4.2.0", features = ["serde"] }
221+
step_planning = { path = "crates/step_planning" }
222+
step_planning_solver = { path = "crates/step_planning_solver" }
217223
structopt = "0.3.26"
218224
syn = { version = "2.0.98", features = ["extra-traits", "full"] }
219225
systemd = "0.10.0"

crates/bevyhavior_simulator/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ serde = { workspace = true }
4242
serde_json = { workspace = true }
4343
spl_network = { workspace = true }
4444
spl_network_messages = { workspace = true }
45+
step_planning = { workspace = true }
4546
tokio = { workspace = true }
4647
tokio-util = { workspace = true }
4748
types = { workspace = true }

crates/bevyhavior_simulator/src/autoref.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ use bevy::{
1313
use coordinate_systems::{Field, Ground};
1414
use linear_algebra::{point, vector, Isometry2};
1515
use spl_network_messages::{GameState, Penalty, SubState, Team};
16+
use step_planning::traits::Length;
1617
use types::{
1718
ball_position::SimulatorBallState,
1819
field_dimensions::{FieldDimensions, Half, Side},
1920
motion_command::MotionCommand,
20-
planned_path::PathSegment,
2121
};
2222

2323
use crate::{
@@ -71,11 +71,7 @@ pub fn autoref(
7171
MotionCommand::Unstiff
7272
| MotionCommand::Penalized
7373
| MotionCommand::Stand { .. } => false,
74-
MotionCommand::Walk { path, .. }
75-
if path.segments.iter().map(PathSegment::length).sum::<f32>() < 0.01 =>
76-
{
77-
false
78-
}
74+
MotionCommand::Walk { path, .. } if path.length() < 0.01 => false,
7975
_ => true,
8076
}
8177
});

crates/bevyhavior_simulator/src/bin/intercept_ball.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ fn startup(
3333
) {
3434
let mut robot = Robot::new(PlayerNumber::One);
3535
*robot.ground_to_field_mut() = Isometry2::from_parts(vector![-2.0, 0.0], 0.0);
36-
robot.parameters.step_planner.max_step_size.forward = 1.0;
37-
robot.parameters.step_planner.max_step_size.left = 1.0;
36+
robot.parameters.step_planner.walk_volume_extents.forward = 1.0;
37+
robot.parameters.step_planner.walk_volume_extents.outward = 1.0;
3838
robot.parameters.step_planner.request_scale = Step {
3939
forward: 1.0,
4040
left: 1.0,

crates/bevyhavior_simulator/src/bin/kick_in.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@ fn update(
4242
robots: Query<&mut Robot>,
4343
mut exit: EventWriter<AppExit>,
4444
) {
45-
if time.ticks() == 6000 {
45+
if time.ticks() == 5000 {
4646
game_controller_commands.send(GameControllerCommand::SetSubState(
4747
Some(SubState::KickIn),
4848
Team::Hulks,
4949
None,
5050
));
5151
}
52-
if time.ticks() >= 6005 && time.ticks() < 6000 + FREE_KICK_DURATION_IN_TICKS {
52+
if time.ticks() >= 5005 && time.ticks() < 5000 + FREE_KICK_DURATION_IN_TICKS {
5353
for robot in robots.iter() {
5454
if robot
5555
.database
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use std::f32::consts::FRAC_PI_2;
2+
3+
use bevy::prelude::*;
4+
5+
use bevyhavior_simulator::{
6+
game_controller::{GameController, GameControllerCommand},
7+
robot::Robot,
8+
time::{Ticks, TicksTime},
9+
};
10+
use geometry::{arc::Arc, circle::Circle, direction::Direction, line_segment::LineSegment};
11+
use linear_algebra::{point, vector, Isometry2, Orientation2, Point2};
12+
use scenario::scenario;
13+
use spl_network_messages::{GameState, PlayerNumber};
14+
use types::{
15+
motion_command::{ArmMotion, HeadMotion, MotionCommand, OrientationMode, WalkSpeed},
16+
planned_path::{Path, PathSegment},
17+
};
18+
19+
#[scenario]
20+
fn mpc_step_planner_optimizer(app: &mut App) {
21+
app.add_systems(Startup, startup);
22+
app.add_systems(Update, update);
23+
}
24+
25+
fn startup(
26+
mut commands: Commands,
27+
mut game_controller_commands: EventWriter<GameControllerCommand>,
28+
) {
29+
commands.spawn(Robot::new(PlayerNumber::Seven));
30+
31+
game_controller_commands.send(GameControllerCommand::SetGameState(GameState::Playing));
32+
}
33+
34+
fn update(
35+
_game_controller: ResMut<GameController>,
36+
time: Res<Time<Ticks>>,
37+
mut exit: EventWriter<AppExit>,
38+
mut robots: Query<&mut Robot>,
39+
) {
40+
let mut robot = robots.single_mut();
41+
42+
robot.database.main_outputs.ground_to_field =
43+
Some(Isometry2::from_parts(vector![-1.0, -1.0], FRAC_PI_2));
44+
robot.parameters.behavior.injected_motion_command = Some(MotionCommand::Walk {
45+
head: HeadMotion::ZeroAngles,
46+
left_arm: ArmMotion::Swing,
47+
right_arm: ArmMotion::Swing,
48+
speed: WalkSpeed::Normal,
49+
path: Path {
50+
segments: vec![
51+
PathSegment::LineSegment(LineSegment(Point2::origin(), point![0.3, 0.0])),
52+
PathSegment::Arc(Arc {
53+
circle: Circle {
54+
center: point![0.3, 0.3],
55+
radius: 0.3,
56+
},
57+
start: Orientation2::new(3.0 * FRAC_PI_2),
58+
end: Orientation2::new(0.0),
59+
direction: Direction::Counterclockwise,
60+
}),
61+
PathSegment::LineSegment(LineSegment(point![0.6, 0.3], point![0.6, 0.8])),
62+
],
63+
},
64+
orientation_mode: OrientationMode::Unspecified,
65+
target_orientation: Orientation2::identity(),
66+
distance_to_be_aligned: 0.1,
67+
});
68+
69+
let optimizer_steps = time.ticks() as usize;
70+
robots
71+
.single_mut()
72+
.parameters
73+
.step_planner
74+
.optimization_parameters
75+
.optimizer_steps = optimizer_steps;
76+
77+
println!("tick {}: {optimizer_steps} steps", time.ticks());
78+
79+
if time.ticks() >= 500 {
80+
exit.send(AppExit::Success);
81+
}
82+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
use std::f32::consts::TAU;
2+
3+
use bevy::prelude::*;
4+
5+
use bevyhavior_simulator::{
6+
game_controller::GameControllerCommand,
7+
robot::Robot,
8+
time::{Ticks, TicksTime},
9+
};
10+
use linear_algebra::{point, Isometry2, Orientation2, Point2};
11+
use scenario::scenario;
12+
use spl_network_messages::{GameState, PlayerNumber};
13+
use types::{
14+
motion_command::{ArmMotion, HeadMotion, MotionCommand, OrientationMode, WalkSpeed},
15+
planned_path::{Path, PathSegment},
16+
};
17+
18+
#[scenario]
19+
fn demonstration(app: &mut App) {
20+
app.add_systems(Startup, startup);
21+
app.add_systems(Update, update);
22+
}
23+
24+
fn startup(
25+
mut commands: Commands,
26+
mut game_controller_commands: EventWriter<GameControllerCommand>,
27+
) {
28+
commands.spawn(Robot::new(PlayerNumber::Seven));
29+
game_controller_commands.send(GameControllerCommand::SetGameState(GameState::Playing));
30+
}
31+
32+
fn update(time: Res<Time<Ticks>>, mut exit: EventWriter<AppExit>, mut robots: Query<&mut Robot>) {
33+
let mut robot = robots.iter_mut().next().unwrap();
34+
35+
robot
36+
.parameters
37+
.step_planner
38+
.optimization_parameters
39+
.optimizer_steps = 100;
40+
robot
41+
.parameters
42+
.step_planner
43+
.optimization_parameters
44+
.warm_start = false;
45+
robot.database.main_outputs.ground_to_field = Some(Isometry2::identity());
46+
47+
let angle = 0.01 * time.ticks() as f32;
48+
let (sin, cos) = angle.sin_cos();
49+
let turn_count = (angle / TAU) as usize;
50+
51+
let orbiting_point = point![cos, sin] * 0.4;
52+
let orientation = Orientation2::from_cos_sin_unchecked(cos, sin);
53+
54+
let (path, orientation_mode, target_orientation) = match turn_count {
55+
0 => (
56+
Path {
57+
segments: vec![PathSegment::LineSegment(
58+
geometry::line_segment::LineSegment(Point2::origin(), orbiting_point),
59+
)],
60+
},
61+
OrientationMode::Unspecified,
62+
Orientation2::identity(),
63+
),
64+
1 => (
65+
Path {
66+
segments: vec![PathSegment::LineSegment(
67+
geometry::line_segment::LineSegment(Point2::origin(), orbiting_point),
68+
)],
69+
},
70+
OrientationMode::Unspecified,
71+
orientation,
72+
),
73+
2 => (
74+
Path {
75+
segments: vec![PathSegment::LineSegment(
76+
geometry::line_segment::LineSegment(Point2::origin(), point![0.4, 0.0]),
77+
)],
78+
},
79+
OrientationMode::LookTowards {
80+
direction: orientation,
81+
tolerance: 0.0,
82+
},
83+
orientation,
84+
),
85+
_ => {
86+
exit.send(AppExit::Success);
87+
return;
88+
}
89+
};
90+
91+
robot.parameters.behavior.injected_motion_command = Some(MotionCommand::Walk {
92+
path,
93+
orientation_mode,
94+
target_orientation,
95+
distance_to_be_aligned: 0.1,
96+
head: HeadMotion::ZeroAngles,
97+
left_arm: ArmMotion::Swing,
98+
right_arm: ArmMotion::Swing,
99+
speed: WalkSpeed::Normal,
100+
});
101+
}

crates/control/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,7 @@ serde = { workspace = true }
3838
smallvec = { workspace = true }
3939
spl_network_messages = { workspace = true }
4040
splines = { workspace = true }
41+
step_planning = { workspace = true }
42+
step_planning_solver = { workspace = true }
4143
types = { workspace = true }
4244
walking_engine = { workspace = true }

crates/control/src/behavior/defend.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use spl_network_messages::{GamePhase, SubState, Team};
1313
use types::{
1414
field_dimensions::{FieldDimensions, Side},
1515
filtered_game_controller_state::FilteredGameControllerState,
16-
motion_command::{JumpDirection, MotionCommand, WalkSpeed},
16+
motion_command::{JumpDirection, MotionCommand, OrientationMode, WalkSpeed},
1717
parameters::{KeeperMotionParameters, RolePositionsParameters},
1818
path_obstacles::PathObstacle,
1919
world_state::{BallState, WorldState},
@@ -68,6 +68,8 @@ impl<'cycle> Defend<'cycle> {
6868
self.look_action.execute(),
6969
path_obstacles_output,
7070
walk_speed,
71+
// TODO(rmburg): maybe change this instead of having a large distance_to_be_aligned?
72+
OrientationMode::AlignWithPath,
7173
distance_to_be_aligned,
7274
hysteresis,
7375
)

0 commit comments

Comments
 (0)