Using JuMP to model a slider-crank mechanism

Hi! I was actually able to get this working wtih help from @araujoms on slack, but documenting here for future Googlers.

I am trying to model a slider-crank mechanism like this:

I want to minimize the height of the system for a certain amount of travel of the slider box.

Here is my original (incorrect!!) code:

using JuMP, Ipopt

model = Model(Ipopt.Optimizer)

# length of crank shaft off the motor shaft
@variable(model, r_crank >= 0, start = 3)

# length of arm from end of crank to connection point on slider block
@variable(model, r_arm >= 0, start = 8)

# Clearance from lowest point of crank shaft to the line of action for the slider block
@variable(model, 8 >= clearance >= 7, start = 7.5)

# total offset between line of action and motor shaft
offset = r_crank + clearance

# angle formed by the line perpendicular to the line of action which intersects the motor shaft and the arm when it is fully retracted 
θ = acos(offset/r_arm)

x = cos(θ)*r_crank
z = sin(θ)*r_crank
y = x - offset
a = √(r_arm^2 - y^2)

# minimum x-dimension distance from motor shaft to the connection point on the slider block
d_min = sin(θ)*r_arm

# max x-dimension distance from motor shaft to the connection point on the slider block
d_max = z + a

#total distance traveled by slider box
d_diff = d_max - d_min

# we want the slider box to move 15 to 20 cm
@constraint(model, c1, 15 <= d_diff <= 20)

# we want the minimum angle to be at least 45 and at most 90 degrees
@constraint(model, c2, 45*π/180 <= θ <= 90*π/180)

# we'd like the system to be as short as possible
@objective(model, Min, offset)

# We'd like the arm to be as short as possible
@objective(model, Min, r_arm+r_crank)

optimize!(model)

This was throwing the error:

This is Ipopt version 3.14.16, running with linear solver MUMPS 5.7.3.

Number of nonzeros in equality constraint Jacobian...:        0
Number of nonzeros in inequality constraint Jacobian.:        8
Number of nonzeros in Lagrangian Hessian.............:       12

The inequality constraints contain an invalid number

Number of Iterations....: 0

Number of objective function evaluations             = 0
Number of objective gradient evaluations             = 0
Number of equality constraint evaluations            = 0
Number of inequality constraint evaluations          = 1
Number of equality constraint Jacobian evaluations   = 0
Number of inequality constraint Jacobian evaluations = 0
Number of Lagrangian Hessian evaluations             = 0
Total seconds in IPOPT                               = 0.000

EXIT: Invalid number in NLP function or derivative detected.

This code had the following problems:

  1. y could be greater than r_arm so a could be imaginary
  2. You need to use @NLconstraint for constraints that have nonlinear functions. I was corrected below
  3. I didn’t have a constraint to relate the length of the arm to the length of the crank, so I was getting ridiculous results even after the errors were fixed.
  4. I want to be able to pick how much clearance I have with my offset and how far the slider box travels, so that shouldn’t be a variable but a constant input.

In the end, I got this as my final code:

using JuMP, Ipopt
const clearance = 7
const target_distance_traveled = 20
begin
	model = Model(Ipopt.Optimizer)
	register(model, :√, 1, √; autodiff = true)
	# length of crank shaft off the motor shaft
	@variable(model, r_crank >= 0, start = 3)
	# length of arm from end of crank to connection point on slider block
	@variable(model, r_arm >= 0, start = 5*clearance)
	# angle between the vertical line connecting the motor shaft to the line of action and the arm when in the retracted position
	@variable(model, 50*π/180<= θ <= 85*π/180, start = 65*π/180)
	# total offset between line of action and motor shaft
	offset = r_crank + clearance
	# y-dimesion distance from motor shaft 
	x = cos(θ)*r_crank
	z = sin(θ)*r_crank
	y = offset - x
	a = √(r_arm^2 - y^2)
	# minimum x-dimension distance from motor shaft to the connection point on the slider block
	d_min = sin(θ)*r_arm
	# max x-dimension distance from motor shaft to the connection point on the slider block
	d_max = z + a
	#total distance traveled by slider box
	d_diff = d_max - d_min
	@NLconstraint(model, d_diff == target_distance_traveled)
	# need to assert that the arm is longer than the height above the line of action when extended or we get NaN results.
	@NLconstraint(model, r_arm >= y)
	# Need to assert the relationship between the arm and crank length
	@NLconstraint(model, r_arm == 2r_crank + √(y^2+(d_min-z)^2))
	# we'd like the system to be as short as possible
	@objective(model, Min, offset)
	optimize!(model)
end

After these modifications, I’m getting reasonable results and no errors :smiley:

I’m just a hobbyist here with no training in mechanical engineering, optimization, or anything else, so I just want to say what a pleasure the JuMP.jl docs are and how helpful this community is. Thank you @jd-foster, @araujoms , and @odow .

4 Likes

Hi @mrufsvold, thanks for posting this over here (I’ve moved this to the optimization category).

I find it easier to discuss questions here on Discourse than on slack, and the discussion can be searched for and linked to by other people in the future.

I would write your model as:

using JuMP, Ipopt
begin
	clearance = 7
	target_distance_traveled = 20
	model = Model(Ipopt.Optimizer)
	@variables(model, begin
		r_crank >= 0, (start = 3)
		r_arm >= 0, (start = 5*clearance)
		50 <= θ <= 85, (start = 65)
	end)
	@expressions(model, begin
		offset, r_crank + clearance
		x, cosd(θ) * r_crank
		z, sind(θ) * r_crank
		y, offset - x
		d_min, sind(θ) * r_arm
		d_max, z + √(r_arm^2 - y^2)
	end)
	@constraints(model, begin
		d_max - d_min == target_distance_traveled
		r_arm >= y
		r_arm == 2r_crank + √(y^2 + (d_min - z)^2)
	end)
	@objective(model, Min, offset)
	optimize!(model)
end

You don’t need to use the @NL macros or register, and you can use sind and cosd to compute in degrees rather than radians.

so I just want to say what a pleasure the JuMP.jl docs are and how helpful this community is.

Thanks!

2 Likes

What is the benefit of using @expresions instead of doing them as raw assignments like I did?

For simple cases like this: very little.

For more complicated cases, building inside a macro can be more performant: Performance tips · JuMP. This mainly applies if you are building linear expressions with many terms.

I tend to use @expression when I post on this forum just encourage people to get in the habit of using it.

1 Like