{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Mathematical Program MultibodyPlant Tutorial\n", "For instructions on how to run these tutorial notebooks, please see the [index](./index.ipynb).\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This shows examples of:\n", "* Creating a `MultibodyPlant` containing an IIWA arm\n", "* Solve a simple inverse kinematics problem by writing a custom evaluator\n", "for `MathematicalProgram` that can handle both `float` and `AutoDiffXd`\n", "inputs\n", "* Using the custom evaluator in a constraint\n", "* Using the custom evaluator in a cost.\n", "\n", "***To be added***:\n", "* Using `pydrake.multibody.inverse_kinematics`.\n", "* Visualizing with Meshcat.\n", "\n", "### Important Note\n", "\n", "Please review the\n", "[API for `pydrake.multibody.inverse_kinematics`](\n", "https://drake.mit.edu/pydrake/pydrake.multibody.inverse_kinematics.html)\n", "before you delve too far into writing custom evaluators for use with\n", "`MultibodyPlant`. You may find the functionality you want there." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Inverse Kinematics Problem\n", "\n", "In this tutorial, we will be solving a simple inverse kinematics problem to\n", "put Link 7's origin at a given distance from a target position. We will use\n", "`MathematicalProgram` to solve this problem in two different ways: first\n", "using the evaluator as a constraint (with a minimum and maximum distance),\n", "and second using the evaluator as a cost (to get as close as possible).\n", "\n", "For more information about `MathematicalProgram`, please see the\n", "[`MathematicalProgram` Tutorial](./mathematical_program.ipynb)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup\n", "\n", "First, we will import the necessary modules and load a `MultibodyPlant`\n", "containing an IIWA." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "from pydrake.math import RigidTransform\n", "from pydrake.multibody.parsing import Parser\n", "from pydrake.systems.analysis import Simulator\n", "from pydrake.all import MultibodyPlant\n", "\n", "from pydrake.solvers import MathematicalProgram, Solve" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plant_f = MultibodyPlant(0.0)\n", "iiwa_url = (\n", " \"package://drake_models/iiwa_description/sdf/iiwa14_no_collision.sdf\"\n", ")\n", "(iiwa,) = Parser(plant_f).AddModels(url=iiwa_url)\n", "\n", "# Define some short aliases for frames.\n", "W = plant_f.world_frame()\n", "L0 = plant_f.GetFrameByName(\"iiwa_link_0\", iiwa)\n", "L7 = plant_f.GetFrameByName(\"iiwa_link_7\", iiwa)\n", "\n", "plant_f.WeldFrames(W, L0)\n", "plant_f.Finalize()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Writing our Custom Evaluator\n", "\n", "Our evaluator is implemented using the custom evaluator\n", "`link_7_distance_to_target`, since its functionality is not already\n", "handled by existing classes in the `inverse_kinematics` submodule.\n", "\n", "Note that in order to write a custom evaluator in Python, we must explicitly\n", "check for `float` and `AutoDiffXd` inputs, as you will see in the implementation\n", "of `link_7_distance_to_target`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Allocate float context to be used by evaluators.\n", "context_f = plant_f.CreateDefaultContext()\n", "# Create AutoDiffXd plant and corresponding context.\n", "plant_ad = plant_f.ToAutoDiffXd()\n", "context_ad = plant_ad.CreateDefaultContext()\n", "\n", "def resolve_frame(plant, F):\n", " \"\"\"Gets a frame from a plant whose scalar type may be different.\"\"\"\n", " return plant.GetFrameByName(F.name(), F.model_instance())\n", "\n", "# Define target position.\n", "p_WT = [0.1, 0.1, 0.6]\n", "\n", "def link_7_distance_to_target(q):\n", " \"\"\"Evaluates squared distance between L7 origin and target T.\"\"\"\n", " # Choose plant and context based on dtype.\n", " if q.dtype == float:\n", " plant = plant_f\n", " context = context_f\n", " else:\n", " # Assume AutoDiff.\n", " plant = plant_ad\n", " context = context_ad\n", " # Do forward kinematics.\n", " plant.SetPositions(context, iiwa, q)\n", " X_WL7 = plant.CalcRelativeTransform(\n", " context, resolve_frame(plant, W), resolve_frame(plant, L7))\n", " p_TL7 = X_WL7.translation() - p_WT\n", " return p_TL7.dot(p_TL7)\n", "\n", "# WARNING: If you return a scalar for a constraint, or a vector for\n", "# a cost, you may get the following cryptic error:\n", "# \"Unable to cast Python instance to C++ type\"\n", "link_7_distance_to_target_vector = lambda q: [link_7_distance_to_target(q)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Formulating the Optimization Problems" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Formluation 1: Using the Custom Evaluator in a Constraint\n", "\n", "We will formulate and solve the problem with a basic cost and our custom\n", "evaluator in a constraint.\n", "\n", "Note that we use the vectorized version of the evaluator." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "prog = MathematicalProgram()\n", "\n", "q = prog.NewContinuousVariables(plant_f.num_positions())\n", "# Define nominal configuration.\n", "q0 = np.zeros(plant_f.num_positions())\n", "\n", "# Add basic cost. (This will be parsed into a QuadraticCost.)\n", "prog.AddCost((q - q0).dot(q - q0))\n", "\n", "# Add constraint based on custom evaluator.\n", "prog.AddConstraint(\n", " link_7_distance_to_target_vector,\n", " lb=[0.1], ub=[0.2], vars=q)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "result = Solve(prog, initial_guess=q0)\n", "\n", "print(f\"Success? {result.is_success()}\")\n", "print(result.get_solution_result())\n", "q_sol = result.GetSolution(q)\n", "print(q_sol)\n", "\n", "print(f\"Initial distance: {link_7_distance_to_target(q0):.3f}\")\n", "print(f\"Solution distance: {link_7_distance_to_target(q_sol):.3f}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Formulation 2: Using Custom Evaluator in a Cost\n", "\n", "We will formulate and solve the problem, but this time we will use our custom\n", "evaluator in a cost.\n", "\n", "Note that we use the scalar version of the evaluator." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "prog = MathematicalProgram()\n", "\n", "q = prog.NewContinuousVariables(plant_f.num_positions())\n", "# Define nominal configuration.\n", "q0 = np.zeros(plant_f.num_positions())\n", "\n", "# Add custom cost.\n", "prog.AddCost(link_7_distance_to_target, vars=q)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "result = Solve(prog, initial_guess=q0)\n", "\n", "print(f\"Success? {result.is_success()}\")\n", "print(result.get_solution_result())\n", "q_sol = result.GetSolution(q)\n", "print(q_sol)\n", "\n", "print(f\"Initial distance: {link_7_distance_to_target(q0):.3f}\")\n", "print(f\"Solution distance: {link_7_distance_to_target(q_sol):.3f}\")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.9" } }, "nbformat": 4, "nbformat_minor": 2 }