#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include //#include #include #include using namespace psemek; namespace { struct bone { geom::point position; geom::vector direction; geom::vector velocity; float angular_velocity; float length; float width; float mass = 0.f; float inertia = 0.f; }; struct joint { std::size_t i0, i1; float s0, s1; // angle range max should be in [0, 2*pi] // angle range min should be less or equal to angle range max std::optional> angle_range = std::nullopt; }; template struct constraint { std::size_t bone[Bones]; geom::matrix jacobian; geom::vector bias; geom::interval range = geom::interval::full(); }; struct system { float const dt = 0.01f; std::vector bones; std::vector joints; struct selection { std::size_t index; float pos; geom::vector delta; }; void advance(float time, std::optional sel, util::function(system const &)> torque_provider); private: float time_lag_ = 0.f; std::vector old_torque_; void step(std::optional sel, util::function(system const &)> const & torque_provider); template void solve_constraint(constraint const & c); }; void system::advance(float time, std::optional sel, util::function(system const &)> torque_provider) { time_lag_ += time; while (time_lag_ >= dt) { time_lag_ -= dt; step(sel, torque_provider); } } void system::step(std::optional sel, util::function(system const &)> const & torque_provider) { if (old_torque_.size() != joints.size()) old_torque_.assign(joints.size(), 0.f); geom::vector const gravity { 0.f, -10.f }; for (std::size_t i = 0; i < bones.size(); ++i) { auto & b = bones[i]; b.velocity += gravity * dt; } auto torque = torque_provider(*this); for (std::size_t i = 0; i < joints.size(); ++i) { auto const & j = joints[i]; auto & b0 = bones[j.i0]; auto & b1 = bones[j.i1]; float const max_torque_change = 250.f * dt; float d = std::max(std::abs(torque[i] - old_torque_[i]) / max_torque_change, 1.f); old_torque_[i] = old_torque_[i] + (torque[i] - old_torque_[i]) / d; b0.angular_velocity -= old_torque_[i] * dt / b0.inertia; b1.angular_velocity += old_torque_[i] * dt / b1.inertia; } for (std::size_t i = 0; i < bones.size(); ++i) { auto & b = bones[i]; b.position += b.velocity * dt; b.direction = geom::normalized(b.direction + geom::ort(b.direction) * b.angular_velocity * dt); } std::vector> constraints_1_1; std::vector> constraints_1_2; std::vector> constraints_2_1; std::vector> constraints_2_2; if (sel) { auto & b = bones[sel->index]; constraint<1, 2> & c = constraints_1_2.emplace_back(); c.bone[0] = sel->index; c.jacobian[0][0] = 1.f; c.jacobian[0][1] = 0.f; c.jacobian[0][2] = sel->pos * b.length / 2.f; c.jacobian[0][3] = 0.f; c.jacobian[1][0] = 0.f; c.jacobian[1][1] = 1.f; c.jacobian[1][2] = 0.f; c.jacobian[1][3] = sel->pos * b.length / 2.f; c.bias = sel->delta * 0.5f / dt; } for (std::size_t i = 0; i < bones.size(); ++i) { auto & b = bones[i]; float const depth_factor = 0.5f / dt; float const bounce = 0.05f; float const friction = 0.05f; auto push_1 = [&](float depth, float s, geom::vector const & v){ constraint<1, 1> & c = constraints_1_1.emplace_back(); c.bone[0] = i; c.jacobian[0][0] = 0.f; c.jacobian[0][1] = 1.f; c.jacobian[0][2] = 0.f; c.jacobian[0][3] = b.length / 2.f * s; c.bias[0] = v[1] - depth * depth_factor; c.range.min = 0.f; constraint<1, 1> & fc = constraints_1_1.emplace_back(); fc.bone[0] = i; fc.jacobian[0][0] = 1.f; fc.jacobian[0][1] = 0.f; fc.jacobian[0][2] = s * b.length / 2.f; fc.jacobian[0][3] = 0.f; fc.bias = fc.bias.zero(); fc.range.min = -friction; fc.range.max = friction; }; std::optional d0, d1; auto p0 = b.position - b.direction * b.length / 2.f; auto p1 = b.position + b.direction * b.length / 2.f; auto v0 = b.velocity - geom::ort(b.direction) * b.angular_velocity * b.length / 2.f; auto v1 = b.velocity + geom::ort(b.direction) * b.angular_velocity * b.length / 2.f; if (p0[1] < b.width / 2.f) { float d = b.width / 2.f - p0[1]; if (d > 0.f) { d0 = d; } } if (p1[1] < b.width / 2.f) { float d = b.width / 2.f - p1[1]; if (d > 0.f) { d1 = d; } } if (d0 && d1) { constraint<1, 2> & c = constraints_1_2.emplace_back(); c.bone[0] = i; c.jacobian[0][0] = 0.f; c.jacobian[0][1] = 1.f; c.jacobian[0][2] = 0.f; c.jacobian[0][3] = -b.length / 2.f; c.jacobian[1][0] = 0.f; c.jacobian[1][1] = 1.f; c.jacobian[1][2] = 0.f; c.jacobian[1][3] = b.length / 2.f; c.bias[0] = - (*d0) * depth_factor + v0[1] * bounce; c.bias[1] = - (*d1) * depth_factor + v1[1] * bounce; c.range.min = 0.f; constraint<1, 2> & fc = constraints_1_2.emplace_back(); fc.bone[0] = i; fc.jacobian[0][0] = 1.f; fc.jacobian[0][1] = 0.f; fc.jacobian[0][2] = -b.length / 2.f; fc.jacobian[0][3] = 0.f; fc.jacobian[1][0] = 1.f; fc.jacobian[1][1] = 0.f; fc.jacobian[1][2] = b.length / 2.f; fc.jacobian[1][3] = 0.f; fc.range.min = -friction; fc.range.max = friction; fc.bias = fc.bias.zero(); } else if (d0) { push_1(*d0, -1.f, v0 * bounce); } else if (d1) { push_1(*d1, 1.f, v1 * bounce); } } for (auto const & j : joints) { constraint<2, 2> & c = constraints_2_2.emplace_back(); auto & b0 = bones[j.i0]; auto & b1 = bones[j.i1]; c.bone[0] = j.i0; c.bone[1] = j.i1; c.jacobian = c.jacobian.zero(); c.jacobian[0][0] = -1.f; c.jacobian[0][2] = -j.s0 * b0.length / 2.f; c.jacobian[0][4] = 1.f; c.jacobian[0][6] = j.s1 * b1.length / 2.f; c.jacobian[1][1] = -1.f; c.jacobian[1][3] = -j.s0 * b0.length / 2.f; c.jacobian[1][5] = 1.f; c.jacobian[1][7] = j.s1 * b1.length / 2.f; c.bias = ((b1.position + b1.direction * j.s1 * b1.length / 2.f) - (b0.position + b0.direction * j.s0 * b0.length / 2.f)) / dt; if (j.angle_range) { float x = geom::dot(b0.direction, b1.direction); float y = geom::det(b0.direction, b1.direction); // a \in [-pi, pi] float a = std::atan2(y, x); // j.angle_range->max \in [0, 2*pi] // j.angle_range->min \in [-2*pi, 2*pi] // j.angle_range->center() \in [-pi, 2*pi] // delta \in [-3*pi, 2*pi] float delta = a - j.angle_range->center(); if (delta > geom::pi) delta -= 2.f * geom::pi; if (delta < -geom::pi) delta += 2.f * geom::pi; // delta in [-pi, pi] float const half_length = j.angle_range->length() / 2.f; if (std::abs(delta) > half_length) { float const factor = 2.f; constraint<2, 1> & c = constraints_2_1.emplace_back(); c.bone[0] = j.i0; c.bone[1] = j.i1; c.jacobian = c.jacobian.zero(); c.jacobian[0][2] = - y * b1.direction[0] + x * b1.direction[1]; c.jacobian[0][3] = - y * b1.direction[1] - x * b1.direction[0]; c.jacobian[0][6] = - y * b0.direction[0] - x * b0.direction[1]; c.jacobian[0][7] = - y * b0.direction[1] + x * b0.direction[0]; if (delta > 0.f) { c.bias[0] = (delta - half_length) * factor / dt; c.range.max = 0.f; } else { c.bias[0] = (delta + half_length) * factor / dt; c.range.min = 0.f; } } } } std::size_t const steps = 1; for (std::size_t step = 0; step < steps; ++step) { for (auto const & c : constraints_1_1) { solve_constraint(c); } for (auto const & c : constraints_1_2) { solve_constraint(c); } for (auto const & c : constraints_2_1) { solve_constraint(c); } for (auto const & c : constraints_2_2) { solve_constraint(c); } } } template void system::solve_constraint(constraint const & c) { geom::vector v; auto j_inv_mass = c.jacobian; for (std::size_t i = 0; i < B; ++i) { bone const & b = bones[c.bone[i]]; v[4 * i + 0] = b.velocity[0]; v[4 * i + 1] = b.velocity[1]; v[4 * i + 2] = -b.angular_velocity * b.direction[1]; v[4 * i + 3] = b.angular_velocity * b.direction[0]; for (std::size_t j = 0; j < C; ++j) { j_inv_mass[j][4 * i + 0] /= b.mass; j_inv_mass[j][4 * i + 1] /= b.mass; j_inv_mass[j][4 * i + 2] /= b.inertia; j_inv_mass[j][4 * i + 3] /= b.inertia; } } geom::vector l = *geom::solve(j_inv_mass * geom::transpose(c.jacobian), - c.jacobian * v - c.bias); for (std::size_t j = 0; j < C; ++j) l[j] = geom::clamp(l[j], c.range); auto p = geom::transpose(j_inv_mass) * l; for (std::size_t i = 0; i < B; ++i) { bone & b = bones[c.bone[i]]; b.velocity[0] += p[4 * i + 0]; b.velocity[1] += p[4 * i + 1]; b.angular_velocity += geom::det(b.direction, geom::vector{p[4 * i + 2], p[4 * i + 3]}); } } struct controller { // inputs should be equal to number of bones * 7 // outputs should be equal to number of joints static constexpr std::size_t inputs = 24 * 7; static constexpr std::size_t outputs = 23; // static constexpr std::size_t layer1 = outputs; // static constexpr std::size_t layer2 = 4; // static constexpr std::size_t layer3 = outputs; //static constexpr std::size_t param_count = (inputs + 1) * outputs; static constexpr float max_output = 20.f; geom::matrix m1; geom::vector t1; // geom::matrix m1; // geom::vector t1; // geom::matrix m2; // geom::vector t2; // geom::matrix m3; // geom::vector t3; //Eigen::VectorXf to_eigen() const; //void from_eigen(Eigen::VectorXf const & v); int generation = 0; template void randomize(RNG && rng, float amplitude); template void mutate(RNG && rng, float amplitude); void reset(); geom::vector apply(system const & s) const; void read(std::istream & is); void write(std::ostream & os) const; static float activation(float x); template static geom::vector activation(geom::vector v); template static void read(std::istream & is, T & x); template static void write(std::ostream & os, T const & x); }; template void controller::randomize(RNG && rng, float amplitude) { generation = 0; random::uniform_distribution d{-1.f, 1.f}; auto visit_m = [&rng, &d, amplitude](auto & m) { for (std::size_t i = 0; i < m.rows(); ++i) { for (std::size_t j = 0; j < m.columns(); ++j) { m[i][j] = d(rng) * amplitude; } } }; auto visit_v = [&rng, &d, amplitude](auto & v) { for (std::size_t i = 0; i < v.dimension(); ++i) { v[i] = d(rng) * amplitude; } }; visit_m(m1); // visit_m(m2); // visit_m(m3); visit_v(t1); // visit_v(t2); // visit_v(t3); } /* Eigen::VectorXf controller::to_eigen() const { Eigen::VectorXf vec; vec.setZero(param_count); auto p = std::copy(m1.coords, m1.coords + inputs * outputs, vec.data()); std::copy(t1.coords, t1.coords + outputs, p); return vec; } void controller::from_eigen(Eigen::VectorXf const & v) { if (v.size() != param_count) throw std::runtime_error("Wrong data size"); std::copy(v.data(), v.data() + inputs * outputs, m1.coords); std::copy(v.data() + inputs * outputs, v.data() + (inputs + 1) * outputs, t1.coords); } */ template void controller::mutate(RNG && rng, float amplitude) { random::normal_distribution d{}; auto visit_m = [&rng, &d, amplitude](auto & m) { for (std::size_t i = 0; i < m.rows(); ++i) { for (std::size_t j = 0; j < m.columns(); ++j) { m[i][j] += d(rng) * amplitude; } } }; auto visit_v = [&rng, &d, amplitude](auto & v) { for (std::size_t i = 0; i < v.dimension(); ++i) { v[i] += d(rng) * amplitude; } }; visit_m(m1); // visit_m(m2); // visit_m(m3); visit_v(t1); // visit_v(t2); // visit_v(t3); } void controller::reset() {} geom::vector controller::apply(system const & sys) const { if (sys.bones.size() * 7 != inputs) throw std::runtime_error(util::to_string("Wrong number of inputs: should be ", sys.bones.size() * 7)); if (sys.joints.size() != outputs) throw std::runtime_error(util::to_string("Wrong number of outputs: should be ", sys.joints.size())); geom::vector input; for (std::size_t i = 0; i < inputs / 7; ++i) { input[7 * i + 0] = sys.bones[i].position[0] - sys.bones[0].position[0]; input[7 * i + 1] = sys.bones[i].position[1]; input[7 * i + 2] = sys.bones[i].direction[0]; input[7 * i + 3] = sys.bones[i].direction[1]; input[7 * i + 4] = sys.bones[i].velocity[0]; input[7 * i + 5] = sys.bones[i].velocity[1]; input[7 * i + 6] = sys.bones[i].angular_velocity; } auto r1 = activation(m1 * input + t1); // auto r2 = activation(m2 * r1 + t2); // auto r3 = activation(m3 * r2 + t3); for (std::size_t i = 0; i < sys.joints.size(); ++i) // r3[i] *= 0.5f * (sys.bones[sys.joints[i].i0].mass + sys.bones[sys.joints[i].i1].mass); r1[i] *= 0.5f * (sys.bones[sys.joints[i].i0].mass + sys.bones[sys.joints[i].i1].mass); return r1 * max_output; } template void controller::read(std::istream & is, T & x) { is.read(reinterpret_cast(&x), sizeof(x)); } template void controller::write(std::ostream & os, T const & x) { os.write(reinterpret_cast(&x), sizeof(x)); } void controller::read(std::istream & is) { read(is, m1); read(is, t1); // read(is, m2); // read(is, t2); // read(is, m3); // read(is, t3); } void controller::write(std::ostream & os) const { write(os, m1); write(os, t1); // write(os, m2); // write(os, t2); // write(os, m3); // write(os, t3); } float controller::activation(float x) { return 2.f / (1.f + std::exp(- 2.f * x)) - 1.f; } template geom::vector controller::activation(geom::vector v) { for (std::size_t i = 0; i < N; ++i) v[i] = activation(v[i]); return v; } controller lerp(controller const & c1, controller const & c2, float t) { controller c; c.m1 = geom::lerp(c1.m1, c2.m1, t); // c.m2 = geom::lerp(c1.m2, c2.m2, t); // c.m3 = geom::lerp(c1.m3, c2.m3, t); c.t1 = geom::lerp(c1.t1, c2.t1, t); // c.t2 = geom::lerp(c1.t2, c2.t2, t); // c.t3 = geom::lerp(c1.t3, c2.t3, t); c.generation = std::max(c1.generation, c2.generation) + 1; return c; } struct animation_2d_app : app::app { animation_2d_app(); void on_resize(int width, int height) override; void on_mouse_wheel(int delta) override; void on_key_down(SDL_Keycode key) override; void update() override; void present() override; void update_camera(); geom::box view_bbox; bool centered = false; gfx::painter painter; util::clock<> frame_clock; util::clock<> test_clock; system physics; std::optional selected; float selected_s = 0.f; random::generator rng{random::random_device{}}; async::threadpool bg{"bg"}; enum class mode { train, test, } mode = mode::train; std::vector population; std::size_t const population_size = 1024; std::size_t const max_train_frames = 10.f / physics.dt; std::size_t const max_train_variations = 1; float const position_variation_amplitude = 0.f; float const angle_variation_amplitude = 0.f; std::size_t train_iterations = 0; std::size_t const max_train_iterations = 1024*8; float const initial_variance = 10.f; static constexpr auto mutation_amplitude = [](float t){ return std::pow(10.f, 1.f + geom::lerp(0.f, -2.f, t)); }; //Eigen::VectorXf mean; //Eigen::MatrixXf covariance; float best_score = 0.f; bool const warm_start = false; bool const enable_testing = true; bool testing_control = true; std::string const cache_location = "./runner"; std::size_t test_id = 0; std::vector test_speeds; void reset_state(system & sys) const; float eval_score(controller const & c, random::generator rng) const; void do_train(); void do_optimize(); void do_test(); }; animation_2d_app::animation_2d_app() : app("Animation 2D") { view_bbox[1] = {-1.f, 15.f}; vsync(false); } void animation_2d_app::on_resize(int width, int height) { app::on_resize(width, height); update_camera(); } void animation_2d_app::on_mouse_wheel(int delta) { float p = std::pow(0.8f, delta); view_bbox[1].max *= p; update_camera(); } void animation_2d_app::update_camera() { float ratio = static_cast(width()) / height(); float cx = 0.f; if (centered) { float m = 0.f; for (auto const & b : physics.bones) { cx += b.position[0] * b.mass; m += b.mass; } cx /= m; } else { cx = ratio * view_bbox[1].length() / 2.f; } view_bbox[0] = geom::expand(geom::interval::singleton(cx), ratio * view_bbox[1].length() / 2.f); } void animation_2d_app::on_key_down(SDL_Keycode key) { if (key == SDLK_c) { centered = !centered; update_camera(); } if (key == SDLK_p) { testing_control = !testing_control; } if (mode == mode::train) { if (key == SDLK_s) { train_iterations = max_train_iterations; } } if (mode == mode::test) { bool reset = false; if (key == SDLK_LEFT) { reset = true; test_id = (test_id + population.size() - 1) % population.size(); } if (key == SDLK_RIGHT) { reset = true; test_id = (test_id + 1) % population.size(); } if (reset) { reset_state(physics); test_clock.restart(); float shiftx = random::uniform_distribution{-1.f, 1.f}(rng) * position_variation_amplitude; float shifty = random::uniform_distribution{0.f, 1.f}(rng) * position_variation_amplitude; float angle = random::uniform_distribution{-1.f, 1.f}(rng) * geom::rad(angle_variation_amplitude); geom::plane_rotation rot{0, 1, angle}; float miny = 0.f; for (auto & b : physics.bones) { b.position = rot(b.position) + geom::vector{shiftx, shifty}; b.direction = rot(b.direction); miny = std::min(miny, b.position[1] - std::abs(b.direction[1]) * b.length / 2.f - b.width / 2.f); } for (auto & b : physics.bones) b.position[1] -= miny; population[test_id].reset(); test_speeds.clear(); } } } void animation_2d_app::update() { if (mode == mode::train) { do_train(); if (train_iterations >= max_train_iterations) { mode = mode::test; frame_clock.restart(); test_clock.restart(); reset_state(physics); std::ofstream os(cache_location, std::ios::binary); population[0].write(os); population[0].reset(); vsync(true); } } else if (mode == mode::test) { if (!enable_testing) { stop(); return; } do_test(); } } void animation_2d_app::reset_state(system & sys) const { sys.bones.clear(); sys.joints.clear(); /* // Jumper sys.bones.push_back(bone{{0.5f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); sys.bones.push_back(bone{{0.25f, 0.5f}, {0.5f, 1.f}, {0.f, 0.f}, 0.f, std::sqrt(1.25f), 0.25f}); sys.bones.push_back(bone{{0.75f, 1.5f}, {0.5f, 1.f}, {0.f, 0.f}, 0.f, std::sqrt(1.25f), 0.25f}); sys.joints.push_back(joint{0, 1, -1.f, -1.f, geom::interval{geom::rad(60.f), geom::rad(120.f)}}); sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval{geom::rad(-30.f), geom::rad(30.f)}}); //*/ /* // Dachshund float s = 0.5f; float h = 0.3f; sys.bones.push_back(bone{{ -s, h*0.5f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, h, 0.25f}); sys.bones.push_back(bone{{ -s, h*1.5f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, h, 0.25f}); sys.bones.push_back(bone{{ 0.f, h*2.f }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f}); sys.bones.push_back(bone{{ s, h*1.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, h, 0.25f}); sys.bones.push_back(bone{{ s, h*0.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, h, 0.25f}); sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval{geom::rad( 0.f), geom::rad(45.f)}}); sys.joints.push_back(joint{1, 2, 1.f, -s, geom::interval{geom::rad(240.f), geom::rad(300.f)}}); sys.joints.push_back(joint{2, 3, s, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}}); sys.joints.push_back(joint{3, 4, 1.f, -1.f, geom::interval{geom::rad( 0.f), geom::rad(45.f)}}); //*/ /* // Cube sys.bones.push_back(bone{{0.5f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); sys.bones.push_back(bone{{1.f, 0.5f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); sys.bones.push_back(bone{{0.5f, 1.f}, {-1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); sys.bones.push_back(bone{{0.f, 0.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval{geom::rad(60.f), geom::rad(120.f)}}); sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval{geom::rad(60.f), geom::rad(120.f)}}); sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval{geom::rad(60.f), geom::rad(120.f)}}); sys.joints.push_back(joint{3, 0, 1.f, -1.f, geom::interval{geom::rad(60.f), geom::rad(120.f)}}); //*/ /* // Cube with tiny legs sys.bones.push_back(bone{{0.5f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); sys.bones.push_back(bone{{1.f, 0.5f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); sys.bones.push_back(bone{{0.5f, 1.f}, {-1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); sys.bones.push_back(bone{{0.f, 0.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval{geom::rad(60.f), geom::rad(120.f)}}); sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval{geom::rad(60.f), geom::rad(120.f)}}); sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval{geom::rad(60.f), geom::rad(120.f)}}); sys.joints.push_back(joint{3, 0, 1.f, -1.f, geom::interval{geom::rad(60.f), geom::rad(120.f)}}); //*/ /* // Roller int n = 2; for (int i = 0; i < n; ++i) sys.bones.push_back(bone{{i + 0.5f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); sys.bones.push_back(bone{{n + 0.25f, std::sqrt(3.f) * 0.25f}, {0.5f, std::sqrt(3.f) * 0.5}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); sys.bones.push_back(bone{{n + 0.25f, std::sqrt(3.f) * 0.75f}, {-0.5f, std::sqrt(3.f) * 0.5}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); for (int i = 0; i < n; ++i) sys.bones.push_back(bone{{n - i - 0.5f, std::sqrt(3.f)}, {-1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); sys.bones.push_back(bone{{-0.25f, std::sqrt(3.f) * 0.75f}, {-0.5f, -std::sqrt(3.f) * 0.5}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); sys.bones.push_back(bone{{-0.25f, std::sqrt(3.f) * 0.25f}, {0.5f, -std::sqrt(3.f) * 0.5}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); for (int i = 0; i < 2 * n + 4; ++i) sys.joints.push_back(joint{i, (i + 1) % (2 * n + 4), 1.f, -1.f, geom::interval{geom::rad(0.f), geom::rad(60.f)}}); //*/ /* // Worm int n = 5; for (int i = 0; i < n; ++i) sys.bones.push_back(bone{{i + 0.5f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); for (int i = 0; i + 1 < n; ++i) sys.joints.push_back(joint{i, i + 1, 1.f, -1.f, geom::interval{geom::rad(-30.f), geom::rad(30.f)}}); //*/ // Caterpillar int n = 8; for (int i = 0; i < n; ++i) sys.bones.push_back(bone{{i + 0.5f, 0.5f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); for (int i = 0; i < n; ++i) { sys.bones.push_back(bone{{i + 0.25f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f}); sys.bones.push_back(bone{{i + 0.25f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f}); } for (int i = 0; i + 1 < n; ++i) sys.joints.push_back(joint{i, i + 1, 1.f, -1.f, geom::interval{geom::rad(-30.f), geom::rad(30.f)}}); for (int i = 0; i < n; ++i) { sys.joints.push_back(joint{i, n + 2 * i + 0, -0.5f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}}); sys.joints.push_back(joint{i, n + 2 * i + 1, 0.5f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}}); } //*/ /* // Runner-1 sys.bones.push_back(bone{{0.f, 0.5f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); sys.bones.push_back(bone{{0.5f, 1.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); sys.bones.push_back(bone{{1.5f, 1.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); sys.bones.push_back(bone{{2.f, 0.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f}); sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}}); sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval{geom::rad(-15.f), geom::rad( 15.f)}}); sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}}); //*/ /* // Runner: dog sys.bones.push_back(bone{{-1.f, 0.25f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f}); sys.bones.push_back(bone{{-1.f, 0.75f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f}); sys.bones.push_back(bone{{ 0.f, 1.f }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f}); sys.bones.push_back(bone{{ 1.f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f}); sys.bones.push_back(bone{{ 1.f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f}); sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval{geom::rad( 0.f), geom::rad(45.f)}}); sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}}); sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}}); sys.joints.push_back(joint{3, 4, 1.f, -1.f, geom::interval{geom::rad( 0.f), geom::rad(45.f)}}); //*/ /* // Runner: goat sys.bones.push_back(bone{{-1.f, 0.25f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f}); sys.bones.push_back(bone{{-1.f, 0.75f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f}); sys.bones.push_back(bone{{ 0.f, 1.f }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f}); sys.bones.push_back(bone{{ 1.f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f}); sys.bones.push_back(bone{{ 1.f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f}); sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval{geom::rad(-45.f), geom::rad( 0.f)}}); sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}}); sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}}); sys.joints.push_back(joint{3, 4, 1.f, -1.f, geom::interval{geom::rad(-45.f), geom::rad( 0.f)}}); //*/ /* // Runner: left sys.bones.push_back(bone{{-1.f, 0.25f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f}); sys.bones.push_back(bone{{-1.f, 0.75f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f}); sys.bones.push_back(bone{{ 0.f, 1.f }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f}); sys.bones.push_back(bone{{ 1.f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f}); sys.bones.push_back(bone{{ 1.f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f}); sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval{geom::rad(-45.f), geom::rad( 0.f)}}); sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}}); sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}}); sys.joints.push_back(joint{3, 4, 1.f, -1.f, geom::interval{geom::rad( 0.f), geom::rad( 45.f)}}); //*/ /* // Runner: right sys.bones.push_back(bone{{-1.f, 0.25f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f}); sys.bones.push_back(bone{{-1.f, 0.75f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f}); sys.bones.push_back(bone{{ 0.f, 1.f }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f}); sys.bones.push_back(bone{{ 1.f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f}); sys.bones.push_back(bone{{ 1.f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f}); sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval{geom::rad( 0.f), geom::rad( 45.f)}}); sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}}); sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}}); sys.joints.push_back(joint{3, 4, 1.f, -1.f, geom::interval{geom::rad(-45.f), geom::rad( 0.f)}}); //*/ float const bone_density = 1.f; for (auto & b : sys.bones) { b.position[1] += 0.125f; b.direction = geom::normalized(b.direction); b.mass = b.length * b.width * bone_density; b.inertia = b.mass * (b.length * b.length + b.width * b.width) / 12.f; } } float animation_2d_app::eval_score(controller const & c, random::generator rng) const { system physics; std::optional sel; float score = 0.f; for (std::size_t variation = 0; variation < max_train_variations; ++variation) { reset_state(physics); float shiftx = random::uniform_distribution{-1.f, 1.f}(rng) * position_variation_amplitude; float shifty = random::uniform_distribution{0.f, 1.f}(rng) * position_variation_amplitude; float angle = random::uniform_distribution{-1.f, 1.f}(rng) * geom::rad(angle_variation_amplitude); geom::plane_rotation rot{0, 1, angle}; float miny = 0.f; for (auto & b : physics.bones) { b.position = rot(b.position) + geom::vector{shiftx, shifty}; b.direction = rot(b.direction); miny = std::min(miny, b.position[1] - std::abs(b.direction[1]) * b.length / 2.f - b.width / 2.f); } for (auto & b : physics.bones) b.position[1] -= miny; float const dt = physics.dt; bool failure = false; float cur_score = 0.f; float energy = 0.f; float reward = 0.f; static std::vector> hit_floor_points = { // {2, 1.f}, // {2, -1.f}, // {2, 1.f}, // {3, -1.f}, // {4, -1.f}, // {5, -1.f}, }; std::size_t train_frames = max_train_frames * 1.0f; float time = 0.f; std::vector switch_times; for (int i = 0; i < 10; ++i) switch_times.push_back(random::uniform_distribution{0.f, train_frames * dt}(rng)); std::sort(switch_times.begin(), switch_times.end()); for (std::size_t frame = 0; frame < train_frames; ++frame) { bool moving = false; for (auto t : switch_times) { if (time < t) break; moving = !moving; } physics.advance(dt, sel, [&c, &energy, dt](system const & physics){ std::vector torque(physics.joints.size(), 0.f); auto ctrl = c.apply(physics); for (std::size_t i = 0; i < ctrl.dimension(); ++i) torque[i] = ctrl[i]; energy += geom::length(ctrl) * dt; return torque; }); for (auto f : hit_floor_points) { if (f.first >= physics.bones.size()) break; auto const & b = physics.bones[f.first]; auto const p = b.position + f.second * b.direction * b.length / 2.f; if (p[1] <= b.width / 2.f) { failure = true; break; } } // penalty += geom::sqr((1.f - physics.bones[2].direction[0]) * dt); // float const target_speed = 2.f; // penalty += geom::sqr((physics.bones[2].velocity[0] - target_speed) / target_speed) * dt; // penalty -= physics.bones[2].velocity[0] * dt / physics.bones; auto cm_vel = geom::vector::zero(); float mass = 0.f; for (auto const & b : physics.bones) { cm_vel += b.velocity * b.mass; mass += b.mass; } cm_vel /= mass; reward += (cm_vel[0]) * dt; // reward -= energy * 0.1f; if (failure) { cur_score = 0.f; break; } if (frame + 1 == max_train_frames) { float mean_pos_x = 0.f; float mass = 0.f; for (auto const & b : physics.bones) { mean_pos_x += b.position[0] * b.mass; mass += b.mass; } mean_pos_x /= mass; cur_score = reward / max_train_variations; } time += dt; } (void)failure; score += cur_score; } return score; } // Old evolutionary training implementation void animation_2d_app::do_train() { if (population.empty()) { population.resize(population_size); std::ifstream is(cache_location, std::ios::binary); if (warm_start && is) { controller w; w.read(is); for (auto & c : population) { c = w; } } else { for (auto & c : population) c.randomize(rng, initial_variance); } } else { std::size_t const preserved = population.size() / 16; std::size_t const cross_parents = population.size() / 8; std::size_t const cross_new = population.size() / 2; std::vector new_population(population.size()); for (std::size_t i = 0; i < preserved; ++i) { new_population[i] = population[i]; } float const mu = mutation_amplitude((train_iterations * 1.f) / max_train_iterations); for (std::size_t i = preserved; i < preserved + cross_new; ++i) { auto p1 = random::uniform_distribution{0, cross_parents - 1}(rng); auto p2 = random::uniform_distribution{0, cross_parents - 1}(rng); float t = random::uniform_distribution{}(rng); new_population[i] = lerp(population[p1], population[p2], t); new_population[i].mutate(rng, mu); } for (std::size_t i = preserved + cross_new; i < population.size(); ++i) new_population[i].randomize(rng, initial_variance); population = std::move(new_population); } //(void)&controller::to_eigen; //(void)&controller::from_eigen; std::vector> scores(population.size()); std::vector> futures; for (std::size_t i = 0; i < population.size(); ++i) { futures.push_back(bg.dispatch([&, i, rng = rng]() mutable { scores[i] = {eval_score(population[i], rng), i}; })); } bg.wait_all(futures.begin(), futures.end()).get(); std::sort(scores.begin(), scores.end(), [](auto const & p1, auto const & p2){ return p1.first > p2.first; }); std::vector new_population(population.size()); for (std::size_t i = 0; i < population.size(); ++i) { new_population[i] = population[scores[i].second]; } population = std::move(new_population); ++train_iterations; best_score = scores.front().first; } /* // Covariance matrix adaptation void animation_2d_app::do_train() { if (population.empty()) { population.resize(population_size); std::ifstream is(cache_location, std::ios::binary); if (warm_start && is) { controller w; w.read(is); for (auto & c : population) { c = w; c.mutate(rng, initial_variance); } } else { for (auto & c : population) c.randomize(rng, initial_variance); } mean.setZero(controller::param_count); for (auto const & c : population) { Eigen::VectorXf v = c.to_eigen(); // mean += v; } mean /= static_cast(population.size()); covariance.setZero(controller::param_count, controller::param_count); for (auto const & c : population) { Eigen::VectorXf const v = c.to_eigen() - mean; covariance += v * v.transpose(); } covariance /= static_cast(population.size() - 1); } else { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" auto ldlt = covariance.ldlt(); Eigen::MatrixXf const l = ldlt.matrixL(); Eigen::VectorXf const d = ldlt.vectorD(); random::normal_distribution n; Eigen::VectorXf v(controller::param_count); for (std::size_t i = 0; i < population.size(); ++i) { for (std::size_t j = 0; j < controller::param_count; ++j) v[j] = n(rng); for (std::size_t j = 0; j < controller::param_count; ++j) { // if (d[j] < 0.f) throw std::runtime_error(util::to_string("Covariance matrix is not positive-semidefinite: ", d[j])); v[j] *= std::sqrt(std::max(d[j], 0.f)); } population[i].from_eigen(l * v + mean); } #pragma GCC diagnostic pop } std::vector> scores(population.size()); std::vector> futures(population.size()); for (std::size_t i = 0; i < population.size(); ++i) { futures[i] = bg.dispatch([&, i, rng = rng]() mutable { scores[i] = {eval_score(population[i], rng), i}; }); } bg.wait_all(futures.begin(), futures.end()).get(); std::sort(scores.begin(), scores.end(), [](auto const & p1, auto const & p2){ return p1.first > p2.first; }); std::size_t const selected = population.size() / 4; auto weight = [selected](std::size_t rank, float){ return static_cast(selected - rank); }; float sum_weights = 0.f; for (std::size_t i = 0; i < selected; ++i) sum_weights += weight(scores[i].second, scores[i].first); mean.setZero(controller::param_count); for (std::size_t i = 0; i < selected; ++i) { float w = weight(scores[i].second, scores[i].first) / sum_weights; mean += w * population[scores[i].second].to_eigen(); } Eigen::MatrixXf new_covariance; new_covariance.setZero(controller::param_count, controller::param_count); for (std::size_t i = 0; i < selected; ++i) { float w = weight(scores[i].second, scores[i].first) / sum_weights; Eigen::VectorXf const v = population[scores[i].second].to_eigen() - mean; new_covariance += w * v * v.transpose(); } // new_covariance *= static_cast(population.size() - 1) / static_cast(population.size()); float const mu = 0.0001f; covariance = covariance * (1.f - mu) + new_covariance * mu; best_score = scores.front().first; ++train_iterations; (void)lerp; } */ void animation_2d_app::do_test() { std::optional> m; if (mouse()) { m = view_bbox.corner((*mouse())[0] * 1.f / width(), 1.f - (*mouse())[1] * 1.f / height()); } if (!is_left_button_down()) { selected = std::nullopt; if (m) { float selected_distance = std::numeric_limits::infinity(); for (std::size_t i = 0; i < physics.bones.size(); ++i) { auto & b = physics.bones[i]; auto p0 = b.position - b.direction * b.length / 2.f; auto p1 = b.position + b.direction * b.length / 2.f; auto r = p1 - p0; auto d = *m - p0; float t = geom::dot(d, r) / geom::dot(r, r); float distance; if (0.f <= t && t <= 1.f) { distance = geom::length(d - r * t); } else { float d0 = geom::distance(p0, *m); float d1 = geom::distance(p1, *m); if (d0 < d1) { t = 0.f; } else { t = 1.f; } distance = std::min(d0, d1); } if (distance < b.width / 2.f && distance < selected_distance) { selected_distance = distance; selected = i; selected_s = 2.f * t - 1.f; } } } } std::optional sel; if (selected && is_left_button_down()) { auto const & b = physics.bones[*selected]; auto delta = b.position + b.direction * selected_s * b.length / 2.f - *m; sel = system::selection{*selected, selected_s, delta}; } if (testing_control) { physics.advance(frame_clock.restart().count(), sel, [this](system const & physics){ std::vector torque(physics.joints.size()); auto ctrl = population[test_id].apply(physics); for (std::size_t i = 0; i < ctrl.dimension(); ++i) torque[i] = ctrl[i]; return torque; }); } else { physics.advance(frame_clock.restart().count(), sel, [](system const & physics){ return std::vector(physics.joints.size(), 0.f); }); } { auto cm_vel = geom::vector::zero(); float mass = 0.f; for (auto const & b : physics.bones) { cm_vel += b.velocity * b.mass; mass += b.mass; } cm_vel /= mass; test_speeds.push_back(physics.bones[2].velocity[0]); } } void animation_2d_app::present() { update_camera(); gl::ClearColor(0.8f, 0.8f, 0.8f, 1.f); gl::Clear(gl::COLOR_BUFFER_BIT); { float ground_width = 0.1f; painter.line({view_bbox[0].min, -ground_width/2.f}, {view_bbox[0].max, -ground_width/2.f}, ground_width, gfx::black); } for (auto const & b : physics.bones) { auto c = gfx::dark(gfx::blue).as_color_rgba(); painter.line(b.position - b.direction * b.length / 2.f, b.position + b.direction * b.length / 2.f, b.width, c); } if (selected) { auto c = gfx::blue.as_color_rgba(); auto const & b = physics.bones[*selected]; painter.line(b.position - b.direction * b.length / 2.f, b.position + b.direction * b.length / 2.f, b.width, c); } for (auto const & b : physics.bones) { for (float s : {-1.f, 1.f}) { auto c = gfx::dark(gfx::red).as_color_rgba(); auto p = b.position + s * b.direction * b.length / 2.f; if (p[1] < b.width / 2.f) c = gfx::yellow.as_color_rgba(); painter.circle(p, b.width * 0.35f, c); } } painter.render(geom::orthographic_camera{view_bbox}.transform()); float avg_speed = 0.f; if (mode == mode::test) { for (float s : test_speeds) avg_speed += s / test_speeds.size(); int margin = 40; float const step = 1.f; int max_frames_shown = (width() - 2 * margin) / step; int start = std::max(0, static_cast(test_speeds.size() - max_frames_shown)); for (std::size_t i = start; i + 1 < test_speeds.size(); ++i) { float const scale = 2.f; geom::point p0{40.f + (i - start ) * step, 180.f - test_speeds[i ] * scale}; geom::point p1{40.f + (i - start + 1) * step, 180.f - test_speeds[i + 1] * scale}; painter.line(p0, p1, 2.f, gfx::red, false); } } { gfx::painter::text_options opts; opts.c = gfx::black; opts.x = gfx::painter::x_align::left; opts.y = gfx::painter::y_align::top; opts.scale = 2.f; painter.text({40.f, 40.f}, util::to_string(train_iterations, "/", max_train_iterations), opts); painter.text({40.f, 64.f}, util::to_string("Best score: ", std::setprecision(10), best_score), opts); painter.text({40.f, 88.f}, util::to_string("Model: ", test_id, "/", population.size(), ", gen ", population[test_id].generation), opts); painter.text({40.f, 112.f}, util::to_string(util::pretty(test_clock.duration(), std::chrono::milliseconds{1})), opts); painter.text({40.f, 136.f}, util::to_string("View height: ", view_bbox[1].length()), opts); // if (mode == mode::test && !test_speeds.empty()) painter.text({40.f, 136.f}, util::to_string("Speed: ", test_speeds.back()), opts); } painter.render(geom::window_camera{width(), height()}.transform()); } } int main() { return app::main(); }