A Julia/JuMP Introduction
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:
- Understand of basic Julia syntax
- 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.
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 IpoptTo 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,
and
,
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 * distanceNow 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.