Package: pylinkage
PylinkagePylinkage is a Python linkage builder and optimizer.
You can create planar linkages and optimize them with aParticle Swarm Optimization.
It is still in beta, so don't hesitate to post pull request or issues for features you would like to see!.InstallationUsing pipThis package is in the PyPi aspylinkage, and can be installed using:pipinstallpylinkageIt is the recommended way of downloading it.Setting up Virtual EnvironmentWe provide anenvironment.ymlfile for conda.
Useconda env update --file environment.yml --name pylinkage-envto install the requirements in a separate environment.Short demoLet's start with a short demo of the package's capabilities.It is highly Pythonic and emphasizes efficient code. Here is the definition of afour-bar linkage.importpylinkageaspl# Main motorcrank=pl.Crank(0,1,joint0=(0,0),angle=.31,distance=1)# Close the looppin=pl.Pivot(3,2,joint0=crank,joint1=(3,0),distance0=3,distance1=1)# Create the linkagemy_linkage=pl.Linkage(joints=(crank,pin))# Show the resultspl.show_linkage(my_linkage)Cool, isn't it? But the package doesn't stop here as it provides a libraryto achieve any movement.Let's say that you want thepinjoint to stay in top-right corner, with an amplitude of [0, 90] exactly.
You can solve it by yourself, you can ask the code to do it for you.Define evaluation function calledfitness_func, that returns a good score to the linkage
when thepinjoint stays in the first quadrant, and 0 otherwise.Then just run the following code:# Let's set some bounds to keep the dimensions reasonablebounds=pl.generate_bounds(my_linkage.get_num_constraints())# Now comes the "magic" function that solve of issuescore,position,coord=pl.particle_swarm_optimization(eval_func=fitness_func,linkage=my_linkage,bounds=bounds,order_relation=min,)[0]# Let's use the result in our linkage!my_linkage.set_num_constraints(constraints)# Dimensionsmy_linkage.set_coords(init_pos)# Intial positionpl.show_linkage(my_linkage)Tadaaa!
We defined a mechanism, solved an issue and viewed the result in a few lines of code!
And remember, you can define any objective function, so let's give it a try!Complete exampleLet's start with a crank-rockerfour-bar linkage, as a classic mechanism.Joints definitionFirst, we define at least one crank because we want a kinematic simulation.crank=pl.Crank(0,1,joint0=(0,0),angle=0.31,distance=1)Here we are actually doing the following:0, 1: x and y initial coordinates of thetailof the crank link.joint0: the position of the parent Joint to link with, here it is a fixed point in space.
The pin will be created on the position of the parent, which is the head of the crank link.angle: the crank will rotate with this angle, in radians, at each iteration.distance: distance to keep constant between crank link tail and head.Now we add a pin joint to close the kinematic loop.pin=pl.Pivot(3,2,joint0=crank,joint1=(3,0),distance0=3,distance1=1)In human language, here is what is happening:joint0,joint1: first and secondJoints you want to link to, the order is not important.distance0,distance1: distance to keep constant between this joint and his two parents.And here comes the trick:
Why do we specify initial coordinates3, 2? They even seem incompatible with distance to parents/parents' positions!This explanation is simple: mathematically a pin joint the intersection of two circles.
The intersection is often two points.
To choose the starting point, we calculate both intersection (when possible),
then we keep the intersection closer to the previous position as the solution.Wait! A linkage with a single motor and only one pin joint? That doesn't make sense!
: Behind the curtain, many joints are created on the fly.
When you define aCrankjoint, it creates a motoranda pin joint on the crank's link head.
For aPivotjoint, it creates3 pin joints: one on each of its parents' positions, and one its position,
which forms a deformable triangle.
This is why pylinkage is so short to write.Linkage definition and simulationOnce your linkage is finished, you can either use thereloadmethod of eachJointin a loop,
or put everything in aLinkagethat will handle this can of thinks for you.Linkage definition is simple:my_linkage=pl.Linkage(joints=(crank,pin))That's all!Now we want to simulate it and to get the locus ofpin. Just use thestepmethod ofLinkageto make a complete rotation.locus=my_linkage.step()You can also specify the number of steps with theiterationargument, or subdivisions of each iteration withdt.Let's recap.importpylinkageaspl# Main motorcrank=pl.Crank(0,1,joint0=(0,0),angle=.31,distance=1)# Close the looppin=pl.Pivot(3,2,joint0=crank,joint1=(3,0),distance0=3,distance1=1)my_linkage=pl.Linkage(joints=(crank,pin))locus=my_linkage.step()VisualizationFirst thing first, you made a cool linkage, but only you know what it is.
Let's add friendly names to joints, so the communication is simplified.crank.name="B"pin.name="C"# Linkage can also have namesmy_linkage.name="Four-bar linkage"Then you can view your linkage!pl.show_linkage(my_linkage)Last recap, rearranging names:# Main motorcrank=pl.Crank(0,1,joint0=(0,0),angle=.31,distance=1,name="B")# Close the looppin=pl.Pivot(3,2,joint0=crank,joint1=(3,0),distance0=3,distance1=1,name="C")# Linkage definitionmy_linkage=pl.Linkage(joints=(crank,pin),order=(crank,pin),name="My four-bar linkage")# Visualizationpl.show_linkage(my_linkage)OptimizationNow, we want automatic optimization of our linkage, using a certain criterion.
Let's find a four-bar linkage that makes a quarter of a circle.
It is a common problem if you want to build awindshield wiperfor instance.Our objective function, often called the fitness function, is the following:# We save the initial positions because we don't want a completely different movementinit_pos=my_linkage.get_coords()@pl.kinematic_minimizastiondeffitness_func(loci,**kwargs):"""Return how fit the locus is to describe a quarter of circle.It is a minimization problem and the theoretic best score is 0."""# Locus of the Joint 'pin", mast in linkage ordertip_locus=tuple(x[-1]forxinloci)# We get the bounding boxcurr_bb=bounding_box(tip_locus)# We set the reference bounding box, in order (min_y, max_x, max_y, min_x)ref_bb=(0,5,3,0)# Our score is the square sum of the edge distancesreturnsum((pos-ref_pos)**2forpos,ref_posinzip(curr_bb,ref_bb))Please note that it is aminimizationproblem, with 0 as lower bound.
On the first line, you notice a decorator; which plays a crucial role:The decorator arguments are (linkage, constraints), it can also receiveinit_posIt sets the linkage with the constraints.Then it verifies if the linkage can do a complete crank turn.If it can, pass the arguments and the resulting loci (path of joints) to the decorated function.If not, return the penalty. In a minimization problem the penalty will befloat('inf').The decorated function should return the score of this linkage.With this constraint, the best theoretic score is 0.0.Let's start with a candide optimization, thetrial-and-errormethod.
Here it is a serial test of switches.# Exhaustive optimization as an example ONLYscore,position,coord=pl.trials_and_errors_optimization(eval_func=fitness_func,linkage=my_linkage,divisions=25,n_results=1,order_relation=min,)[0]Here the problem is simple enough, so that method takes only a few seconds and returns 0.05.However, with more complex linkages, you need something more robust and more efficient.
Then we will useparticle swarm optimization.
Here are the principles:The parameters are the geometric constraints (the dimensions) of the linkage.A dimension set (an n-uplet) is called aparticleor anagent. Think of it like a bee.The particles move in an n-vectorial space. That is, if we have n geometric constraints, the particles move in an n-D space.Together, the particles form theswarm.Each time they move, their score is evaluated by our fitness function.They know their best score, and know the current score of their neighbor.Together they will try to find the extreme in the space. Here it is a minimum.It is particularly relevant when the fitness function is not resource-greedy.# We reset the linkage (an optimal linkage is not interesting)my_linkage.set_num_constraints(constraints)# As we do for initial positionsmy_linkage.set_coords(init_pos)# Optimization is more efficient with a start spacebounds=pl.generate_bounds(my_linkage.get_num_constraints())score,position,coord=pl.particle_swarm_optimization(eval_func=fitness_func,linkage=my_linkage,bounds=bounds,order_relation=min,)[0]Here the result can vary, but it is rarely above 0.2.So we made something that says it works, let's verify it:With a bit of imagination, you have a wonderful windshield wiper!StructureAs of today, we segment the code in main three parts:linkage.pythis module describes joints and linkagesDue to the geometric approach, joints (instances ofJointobject) are defined without links.TheLinkageclass that will make your code shorter.optimizer.pyproposes three optimizations based on three techniques:The "exhaustive" optimization (exhaustive_optimizationfunction) is a simple grid search optimization method, consisting or trying sequencially all positions. It is here for demonstration purposes only, and you should not use it if you are looking for an efficient technique.The built-in Particle Swarm Optimizer (PSO). I started with it, so it offers a large set of useful options for linkage optimization. However, it is here for legacy purposes, and is much short than the PySwarms module.PSO usingPySwarms. We provide a wrapper function to PySwarm from ljvmiranda921, that will progressively be extended.visualizer.pycan make graphic illustrations of your linkage using matplotlib.It is also used to visualize your n-dimensional swarm, which is not supported by PySwarms.RequirementsPython 3, numpy for calculation, matplotlib for drawing, and standard libraries.
You will also need PySwarms for the Particle Swarm Optimization.ContributingPylinkage is open to contribution.
I may consider any pull request, but I ask you to respect theCODE_OF_CONDUCT.mdand follow the guidelines as defined inCONTRIBUTING.md.