Skip to main content

A Julia/JuMP Introduction

Mitch Phillipson January 31, 2026


This example will introduce the basic syntax of JuMP, a modeling language for optimization problems in Julia. We start with a simple example to introduce syntactic elements of JuMP and compare it to GAMS syntax. Then we will detail a slightly larger example.

I am going to make the following assumptions about your Julia knowledge:

  1. Understand of basic Julia syntax
  2. Familiarity with installing packages and creating environments

Knowledge of GAMS is not necessary, but I will make comparisons to GAMS syntax for those familiar with it.

All the example files can be downloaded from this GitHub repository.

A Simple Scalar JuMP Model

Let’s set up and solve the following simple optimization model in JuMP.

Maximize3x+5ySubject to2x+3y12x+y3x,y0 \begin{align*} \text{Maximize} \quad & 3x + 5y \\ \text{Subject to} \quad & 2x + 3y \leq 12 \\ & -x + y \leq 3 \\ & x, y \geq 0 \end{align*}

The first step with any Julia project to create a new environment with JuMP and Ipopt installed. In your environment, create a new file and import the JuMP and Ipopt packages.

using JuMP
using Ipopt

To initialize a JuMP model, we use the Model() function. We also need to specify the solver we want to use, in this case, Ipopt. JuMP has methods to attach a solver after model initialization, but it’s a good practice to specify it at creation.

M = Model(Ipopt.Optimizer)

Unlike GAMS, our model starts with a name and all variables/constraints are associated to that model object M. You can give this a better name if you’d like.

In this model we have two variables, xx and yy, that are both non-negative. We can define these variables in JuMP using the @variable macro.

@variable(M, x >= 0)
@variable(M, y >= 0)

Notice, we specified we specified the lower bound of each variable on creation. You can also specify upper bounds and other attributes here, we’ll see examples later. The full variable documentation is available here.

The @variable macro puts each variable in the local namespace. You can type x or y into your REPL and see the variable object printed out. In this case, it will just redisplay x and y since they are scalar variables. But it means you can use them directly in expressions later. Variables can also be retrieved from the model object M using the syntax M[:x] or M[:y]. Notice these are symbols, so the variable name is prefixed with a colon :.

Next, we need to define the objective function. In JuMP, we use the @objective macro for this. Since we are maximizing, we use the Max keyword.

@objective(M, Max, 3x + 5y)

There are two constraints in this model. We can define them using the @constraint macro.

@constraint(M, 2x + 3y <= 12)
@constraint(M, cons2, -x + y <= 3)

You should notice we’ve defined our constraints two different ways. The first constraint is unnamed, while the second constraint is named cons2. Naming constraints can be useful for referencing them later, but it’s optional. Type cons2 into your REPL, you’ll see your constraint object printed out. We will use this after we optimize to view the constraint value and dual.

Finally, we can solve the model using the optimize! function.

optimize!(M)

You’ll see Ipopt output in the REPL as it solves the model. After it finishes, we can retrieve the optimal values of our variables and the objective function.

value(x)
println("y = $(value(y))") # Fancier way to print
objective_value(M)
value(cons2)
dual(cons2)

The same model is implemented in GAMS, example1.gms. Compare it to the JuMP version. What are the key differences? If you’re familiar with GAMS, you should notice there are a few simplifications we could make. We defined the variables independently, but they could be defined in a single block. In other words, we could have written:

variables
    x,
    y;

instead of:

variable x;
variable y;

This change does not affect the model, but it simplifies the code and is less typing.

The same is true in Julia. We used the @variable macro twice, but we could have defined both variables using the @variables macro:

@variables(M, begin
    x >= 0
    y >= 0
end)

Notice there are no commas at the end of each line. This is because the begin/end block allows for multiple lines without commas. This is a common pattern in JuMP when defining multiple variables or constraints. There is also an @constraints macro for defining multiple constraints at once.

The Transportation Model

This model will be a translation of the GAMS transportation model.

For this example, you will want to add NamedArrays to your Julia environment. This package makes it easy to work with indexed variables and parameters in JuMP, similar to GAMS parameters.

Model Description

The goal of this model is to minimize the cost of shipping goods from a set of canning plants to a set of markets. Each plant has a certain capacity, and each market has a certain demand. The cost of shipping is proportional to the distance between the plant and the market.

The two canning plants are located in seattle and san diego. The three markets are located in new york, chicago, and topeka. The capacities of the plants and the demands of the markets are given in the following tables:

Canning Plant Capacity
seattle 350
san diego 600
Market Demand
new york 325
chicago 300
topeka 275

The distances between the plants and markets are given in the following table:

new york chicago topeka
seattle 2.5 1.7 1.8
san diego 2.5 1.8 1.4

Find the minimum total cost if each shipment costs $90 per case per thousand miles.

Julia Implementation

We start by importing the necessary packages and defining our sets. Julia does not have a built-in set type like GAMS, so we use arrays of symbols to represent our sets. You can use strings instead of symbols, but there are some advantages to symbols.

using JuMP, Ipopt, NamedArrays

canning_plants = [:seattle, :san_diego]
markets = [:new_york, :chicago, :topeka]

With our sets defined, let’s create a few NamedArrays to hold our parameters: capacity, demand, and distance.

capacity = NamedArray([350, 600], (canning_plants,), ("Canning Plants",))
demand = NamedArray([325, 300, 275], (markets,), ("Markets",))
distance = NamedArray([2.5 1.7 1.8; 2.5 1.8 1.4], (canning_plants, markets), ("Canning Plants", "Markets"))

The third argument to NamedArray is optional, but it helps with readability when printing the arrays. It sets the name of each dimension. Try rerunning the above code, but removing the third argument to see the difference.

The final piece of data we need is the transportation cost per case. This is given as $90 per case per thousand miles. We can calculate this by multiplying the distance by 90/1000.

transport_cost = 90/1000 * distance

Now we can initialize our JuMP model just like before. I like using Ipopt as it is free and open-source. However, there is a wide variety of solvers available for JuMP, both commercial and open-source. Here is a link to the list of available solvers.

M = Model(Ipopt.Optimizer)

We have a single variable x, which represents the number of cases shipped from each canning plant to each market. This variable is indexed over both sets, let’s see the syntax for this:

@variable(M, x[canning_plants, markets] >= 0)

This should be intuitive syntax. You will see it again when defining the constraints, when we do this we will explicit name each index and use it within the constraint expression. This will make sense when we see the full constraints.

Next, we define the objective function. The objective is to minimize the total transportation cost, which is the sum of the cost of shipping from each plant to each market. We can express this using a double summation over both sets.

@objective(M, Min, sum(transport_cost[i,j] * x[i,j] for i in canning_plants, j in markets))

Executing the above line displays the full objective function in the REPL, which is a nice feature of JuMP. It allows you to verify the model is being built as expected.

Next, we define the constraints. There are two sets of constraints: supply constraints for each canning plant and demand constraints for each market.

@constraints(M, begin
    supply[i = canning_plants], sum(x[i,j] for j in markets) <= capacity[i]
    
    demand[j = markets], sum(x[i,j] for i in canning_plants) >= demand[j]
end)

Here, we use the @constraints macro to define multiple constraints at once. Each constraint is named (supply and demand) and indexed over the appropriate set. The syntax i = canning_plants creates a constraint for each element in the canning_plants set. Try running each line below in your REPL to see what is happening:

supply
supply[:seattle]

Finally, we can optimize the model and retrieve the results.

optimize!(M)
value.(x)

Note that the solution we found is different than the GAMS solution. Ours is an interior solution (we used Ipopt), while GAMS found a corner solution using a different solver. Both solutions are optimal, as they have the same objective value.

objective_value(M)

Conclusion

In this example, we introduced the basic syntax of JuMP and compared it to GAMS syntax. We built a simple scalar optimization model and a more complex transportation model. JuMP is a powerful and flexible modeling language that can handle a wide variety of optimization problems. For more information, check out the JuMP documentation.

If you are interested in moving from GAMS to JuMP, reach out to WiNDC directly. We offer professional development services to help with the transition.