This package aims to provide reliable computation techniques in Python to build, simulate and optimize planar leg mechanisms. It is divided in three main parts:
- Linkage conception in simple Python and kinematic optimization relying on pylinkage.
- Leg mechanism definition, with
Walker
heriting from theLinkage
class. - Dynamic simulation and its optimization thanks to genetic algorithms.
The package is hosted on PyPi as leggedsnake, use:
pip install leggedsnake
We provide an environment.yml file for conda. Use conda env update --file environment.yml --name leggedsnake-env
to install the requirements in a separate environment.
Python 3, numpy for calculation, matplotlib for drawing, and standard libraries.
For kinematic optimization you can either use the built-in algorithm, or PySwarms, under MIT license. PySwarms is a much more complexe package which provides quick calculations, however with modern laptops the built-in swarm optimization should be quick enough to fit your needs.
Dynamic optimization relies on multiple packages. First of all it uses Pymunk, made by Victor Blomqvist, as a physics engine. Then you can either use the built-in algorithm, or the GA module from PyGAD. PyGAD is a complete library providing much more than genetic algorithms, so it might be heavy. PyGAD is more complete than the built-in however, so I haven't decided to continue on PyGAD or switch for another solution in the future.
The demo script is strider.py, which demonstrates all the techniques about the Strider linkage.
First, you need to define joints for your Walker
as described in pylinkage documentation. Once your joints (let's say they are in a joint object), you should have something like that:
from pylinkage.linkage import Static, Pivot, Fixed, Crank
from leggedsnake.walker import Walker
# Center of the Walker
A = Static(x=0, y=0, name="A")
B = Crank(1, 0, distance=1, angle=0.31, name="Crank")
# etc... let's say with have joints up to E
my_walker = Walker(
joints=(A, B, C, D, E),
name="My Walker"
)
Walker
is just a herited class of Linkage
, with some useful methods, and behaves quite the same way.
No change compared to a classic linkage optimization. You should use the step
and stride
method from the utility module as fitness functions.
This set of rules should work well for a stride maximisation problem:
#. Rebuild the Walker with the provided set of dimensions, and do a complete turn.
#. If the Walker raise an UnbuildableError, its score is 0 (or - float('inf')
if you use other evaluation functions.
#. Verify if it can pass a certain obstacke using step
function. If not, its score is 0.
#. Eventually mesure the length of its stide with the stride
function. Return this length as its score.
Kinematic optimization is fast, however it can return weird results, and it has no sense of gravity while walking heavily relies on gravity. This is why you may need to use dynamic optimization thanks to Pymunk. However the calculation is much more slower, and you can no longer tests millions of linkages as in PSO (or you will need time). This is why we use genetic algorithm, because it can provide good results with less parents.
We handle everything almost evything world definition to linkage conversion. Appart from the GA parameters, you just have to define a fitness function. Here are the main steps for a maximisation problem:
- Create a function of two arguments, the first one should be the paramaters of the linkage, the second the initial positions for the joints.
- Try to do a revolution in kinematic simulation. If the Walker raises an
UnbuildableError
set its score to-float('inf')
. - Otherwise use this procedure
from leggedsnake import physicsengine as pe
def dynamic_linkage_fitness(walker):
"""
Make the dynamic evalutaion of a Walker.
Return yield and initial position of joints.
"""
world = pe.World()
# We handle all the conversions
world.add_linkage(walker)
# Simulation duration (in seconds)
duration = 40
# Somme of yields
tot = 0
# Motor turned on duration
dur = 0
n = duration * pe.params["camera"]["fps"]
n /= pe.params["simul"]["time_coef"]
for j in range(int(n)):
efficiency, energy = world.update(j)
tot += efficiency
dur += energy
if dur == 0:
return - float('inf'), list()
print("Score:", tot / dur)
# Return 100 times average yield, and initial positions as the final score
return tot / dur, pos
And now, relax while your computer recreates a civilisation of walking machines!
For this part we will focus on the Strider linkage, an exemple file is provided at strider.py. The linkage looks like this:
Looks cool? Let's simulate it dynamically!
Oops! Here is what you get when you forget to add more legs! There is real danger here, because your walker crawls well, you will be able to optimize efficiently the "crawler", which may be not your goal.
Let's add three more leg pairs. Why three? Many legs means more mass and constraints, so less yield and more intensive computations. On the other hand, we always want the center of mass over the support line, which means that if the walker begins to lift a foot (let's say a front foot), and another doesn't come on the ground ahead of it, the linkage will to fall nose to the ground. With more foots we make the "snooping" time shorter, and a total of four leg pairs is a minimum for this unoptimized version.
Let's have a look at the artist:
Use the vizualisation tools provided! The optimization tools should always give you a score with a better fitness, but it might not be what you expected. Tailor your optimization and then go for a long run will make you save a lot of time.
Do not use optimized linkages from the start! The risk is to fall to quickly into a suboptimal solution. They are several mechanisms to prevent that (starting from random position), however it can always have an impact on the rest of the optimization.
Try to minimize the number of elements in the optimizations! You can often use some linkage's properties to reduce the number of simulation parameters. For instance, the Strider linkage has an axial symmetry. While it is irrelevant to use this property in dynamic simulation, you can use "half" your Strider in a kinematic optimization, which is much faster: