Linear programming in Julia with GLPK and JuMP

By: Jaafar Ballout

Re-posted from: https://www.supplychaindataanalytics.com/linear-programming-in-julia-with-glpk-and-jump/

In previous posts we have covered various linear programming examples in both Python and R. We have e.g. introduced lpSolve in R, PuLP in Python, Gekko in Python, MIP in Python, ortools in Python as well as many other packages and modules for linear programming. In this post I will demonstrate how one can implement linear programming in Julia.

Julia is a flexible dynamic language that is appropriate for scientific and numerical computing. Besides, Julia is an open-source project. Its source code is available on GitHub. Using Julia provides access to many packages already developed for this language. For the linear programming example presented in this post I will use the Julia packages GLPK and JuMP.

Financial engineering example utilizing JuMP and GLPK in Julia

Logo of Julia programming language

The demonstrated example is about a bank loan policy. This simple example will help us understand the power of linear programming in solving most of the problems we face in supply chain management and the financial fields. Indeed, Julia will be incorporated to solve the optimization problem. The example is adapted from Hamdy Taha’s book available on Amazon.

Type of loan Interest rate Bad-debt ratio
Personal 0.14 0.1
Car 0.13 0.07
Home 0.12 0.03
Farm 0.125 0.05
Commercial 0.10 0.02

A bank is in the process of devising a loan policy that involves a maximum of $12 million. The table, shown above, provides the data about the available loans. It is important to note that bad debts are unrecoverable and produce no interest revenue. Competition with other financial institutions dictates the allocation of at least 40% of the funds to farm and commercial loans. To assist the housing industry in the region, home loans must equal at least 50% of the personal, car, and home loans. The bank limits the overall ratio of bad debts on all loans to at most 4%.

Developing a mathematical problem statement

The hardest part of any optimization problem is building the mathematical model. The steps to overcome this hardship are as follows:

  1. Define the decision variables
  2. Write down the objective function
  3. Formulate all the constraints

Decison variables

According to the given in the problem, we need to determine the amount of loan in million dollars. The table shows five categories or types of loans. Thus, we need five decision variables corresponding to each category.

  • x1 = personal loans
  • x2 = car loans
  • x3 = home loans
  • x4 = farm loans
  • x5 = commercial loans

Objective function

The objective function is to maximize profits (net return). The net return is the difference between the revenues, generated by interest rate, and losses due to the bad-debt ratio.

Then, the objective function is as follows:

Constraints

Total Loans should be less or equal to 12 million dollars

Farm and Commercial loans equal at least 40% of all loans

Home loans should equal at least 50% of the personal, car, and home loans

Bad debts should not exceed 4% of all loans

Non-negativity

Application of linear programming in Julia

The first step is to add the required packages to solve the linear program. This is done using the <Pkg> which is the built-in package manager in Julia. We can add the needed packages using the following commands in Julia REPL. If Julia is installed, typing Julia at the command line, in the computer command prompt, is enough to open REPL.

Julia REPL
using Pkg
Pkg.add("JuMP")
Pkg.add("GLPK")

The work presented below is done in a Jupyter notebook with a Julia kernel. JuMP is a modeling language for mathematical optimization in Julia while GLPK is the solver that we will use. After importing the packages in Jupyter, we defined the optimization problem by creating a variable BM that stands for Bank Model. The next step is setting the optimizer by declaring the solver (GLPK) and the model (BM). Moving on, we specified the decision variables, constraints, and objective function.

In [1]:

## Importing the necessary packages ## 
using JuMP
using GLPK

In [2]:

## Defining the model ##
BM = Model() # BM stands for Bank Model

## Setting the optimizer ##
set_optimizer(BM,GLPK.Optimizer)

## Define the variables ##
@variable(BM, x1>=0)
@variable(BM, x2>=0)
@variable(BM, x3>=0)
@variable(BM, x4>=0)
@variable(BM, x5>=0)

## Define the constraints ##
@constraint(BM, x1+x2+x3+x4+x5<=12)
@constraint(BM, 0.4x1+0.4x2+0.4x3-0.6x4-0.6x5<=0)
@constraint(BM, 0.5x1+0.5x2-0.5x3<=0)
@constraint(BM, 0.06x1+0.03x2-0.01x3+0.01x4-0.02x5<=0)

## Define the objective function ##
@objective(BM, Max, 0.026x1+0.0509x2+0.0864x3+0.06875x4+0.078x5)

## Run the optimization ##
optimize!(BM)

Declaring the objective function and constraints in Julia is easier because the algebraic equation can be inputted as it is (i.e. no need to show the multiplication operator between the variable and the constant). After running the optimization model, we can view the output of the model as follows:

In [3]:

BM

Out[3]:

A JuMP Model
Maximization problem with:
Variables: 5
Objective function type: AffExpr
`AffExpr`-in-`MathOptInterface.LessThan{Float64}`: 4 constraints
`VariableRef`-in-`MathOptInterface.GreaterThan{Float64}`: 5 constraints
Model mode: AUTOMATIC
CachingOptimizer state: ATTACHED_OPTIMIZER
Solver name: GLPK
Names registered in the model: x1, x2, x3, x4, x5

In [4]:

objective_value(BM)

Out[4]:

0.99648

In [5]:

objective_sense(BM)

Out[5]:

MAX_SENSE::OptimizationSense = 1

In [6]:

println("x1 = ", value.(x1), "\n","x2 = ",value.(x2), "\n", "x3 = ",value.(x3), "\n", "x4 = ",value.(x4), "\n", "x5 = ",value.(x5))
x1 = 0.0
x2 = 0.0
x3 = 7.199999999999999
x4 = 0.0
x5 = 4.8

Analyzing the output of the model, we can see that the bank should spend 7.2 million dollars on home loans and 4.8 million dollars on commercial loans. This distribution will help the bank maximize its net returns (i.e. profits) to 0.99648 million dollars.

Concluding remarks

In this post, we explored the power of linear programming in the banking sector and used Julia to solve the LP optimization problem. It is nice to see that open-source languages can solve these kinds of problems in an easy way without extensive hours of coding. In the future, we will have an insight into non-linear problems in supply chain management.

The post Linear programming in Julia with GLPK and JuMP appeared first on Supply Chain Data Analytics.

News features in DataFrames.jl 1.3: part 3

By: Blog by Bogumił Kamiński

Re-posted from: https://bkamins.github.io/julialang/2021/12/24/selection.html

Introduction

This post continues the presentation of new features added in DataFrames.jl 1.3.0. This time I will discuss what is new in indexing syntax.

The post was written under Julia 1.7.0 and DataFrames.jl 1.3.1.
When running the examples use the --depwarn=yes option when starting Julia.

Adding columns in views

Since DataFrames.jl 1.3 a long requested feature to allow adding columns to
views has been added. As, in general, in a view you can reorder and/or drop
columns this feature is only allowed if a view was created with : as column
selector (remember, that when using : as column selector a view will always
reflect the list of columns of its parent DataFrame). Here is an example:

julia> using DataFrames

julia> df = DataFrame(a=1:3)
3×1 DataFrame
 Row │ a
     │ Int64
─────┼───────
   1 │     1
   2 │     2
   3 │     3

julia> dfv = @view df[[1,3], :]
2×1 SubDataFrame
 Row │ a
     │ Int64
─────┼───────
   1 │     1
   2 │     3

julia> dfv[:, :b] = 4:5
4:5

julia> dfv
2×2 SubDataFrame
 Row │ a      b
     │ Int64  Int64?
─────┼───────────────
   1 │     1       4
   2 │     3       5

julia> df
3×2 DataFrame
 Row │ a      b
     │ Int64  Int64?
─────┼────────────────
   1 │     1        4
   2 │     2  missing
   3 │     3        5

Note that in column :b in df in filtered out rows missing value was
placed.

As noted creating new columns is not allowed if other column selector than :
is passed when creating a view:

julia> dfv = @view df[[1,3], 1:2]
2×2 SubDataFrame
 Row │ a      b
     │ Int64  Int64?
─────┼───────────────
   1 │     1       4
   2 │     3       5

julia> dfv[:, :c] = 4:5
ERROR: ArgumentError: creating new columns in a SubDataFrame that subsets columns of its parent data frame is disallowed

Additionally it is allowed to replace columns in a view when ! selector is
used (here it works for any view as we are not creating new columns):

julia> dfv.a = ["111", "113"]
2-element Vector{String}:
 "111"
 "113"

julia> df
3×2 DataFrame
 Row │ a    b
     │ Any  Int64?
─────┼──────────────
   1 │ 111        4
   2 │ 2    missing
   3 │ 113        5

As you can see the values that were present in filtered-out rows are retained.
If the new values have type not allowed in the current element type of the
column an appropriate type promotion is performed. Here is another example:

julia> df = DataFrame(a=1:3, b=4:6)
3×2 DataFrame
 Row │ a      b
     │ Int64  Int64
─────┼──────────────
   1 │     1      4
   2 │     2      5
   3 │     3      6

julia> dfv = @view df[[1,3], :]
2×2 SubDataFrame
 Row │ a      b
     │ Int64  Int64
─────┼──────────────
   1 │     1      4
   2 │     3      6

julia> dfv.a = 11.0:12.0
11.0:1.0:12.0

julia> dfv.b = 'a':'b'
'a':1:'b'

julia> df
3×2 DataFrame
 Row │ a        b
     │ Float64  Any
─────┼──────────────
   1 │    11.0  a
   2 │     2.0  5
   3 │    12.0  b

Why does adding columns in views matter?

The huge benefit of allowing adding columns in views is as follows: we can make
all standard functions like insertols!, select!, transform! work on
SubDataFrames. This is very useful if you want to perform some operation only
under some condition. Here is a simple example:

julia> df = DataFrame(x = -1:0.2:1)
11×1 DataFrame
 Row │ x
     │ Float64
─────┼─────────
   1 │    -1.0
   2 │    -0.8
   3 │    -0.6
   4 │    -0.4
   5 │    -0.2
   6 │     0.0
   7 │     0.2
   8 │     0.4
   9 │     0.6
  10 │     0.8
  11 │     1.0

julia> transform!(subset(df, :x => ByRow(>(0)), view=true), :x => ByRow(log))
5×2 SubDataFrame
 Row │ x        x_log
     │ Float64  Float64?
─────┼────────────────────
   1 │     0.2  -1.60944
   2 │     0.4  -0.916291
   3 │     0.6  -0.510826
   4 │     0.8  -0.223144
   5 │     1.0   0.0

julia> df
11×2 DataFrame
 Row │ x        x_log
     │ Float64  Float64?
─────┼─────────────────────────
   1 │    -1.0  missing
   2 │    -0.8  missing
   3 │    -0.6  missing
   4 │    -0.4  missing
   5 │    -0.2  missing
   6 │     0.0  missing
   7 │     0.2       -1.60944
   8 │     0.4       -0.916291
   9 │     0.6       -0.510826
  10 │     0.8       -0.223144
  11 │     1.0        0.0

In DataFrames.jl you have to do this in two steps: subset to a view and then
transform!. However, I hope that DataFramesMeta.jl and
DataFrameMacros packages in the coming releases will provide a nicer
syntax for this, allowing to combine transformation and filtering in one step.

Hard deprecation period for broadcasted assignment

Since Julia 1.7 is out a long missing feature is now available.
The feature is that it is allowed to add new columns to a data frame using
the broadcasting assignment with setproperty:

julia> df = DataFrame(a=1:3)
3×1 DataFrame
 Row │ a
     │ Int64
─────┼───────
   1 │     1
   2 │     2
   3 │     3

julia> df.b .= 1
3-element Vector{Int64}:
 1
 1
 1

julia> df
3×2 DataFrame
 Row │ a      b
     │ Int64  Int64
─────┼──────────────
   1 │     1      1
   2 │     2      1
   3 │     3      1

Also views are supported the way we have described earlier:

julia> dfv = view(df, [1, 3], :)
2×2 SubDataFrame
 Row │ a      b
     │ Int64  Int64
─────┼──────────────
   1 │     1      1
   2 │     3      1

julia> dfv.c .= 2
2-element Vector{Int64}:
 2
 2

julia> df
3×3 DataFrame
 Row │ a      b      c
     │ Int64  Int64  Int64?
─────┼───────────────────────
   1 │     1      1        2
   2 │     2      1  missing
   3 │     3      1        2

In essence using setproperty is made almost the same as using the ! row
selector and assignment. Why do I say almost? The reason is that the only
place where it is inconsistent is broadcasted assignment to an existing column:

julia> df.a .= 10
┌ Warning: In the 1.4 release of DataFrames.jl this operation will allocate a new column instead of performing an in-place assignment. To perform an in-place assignment use `df[:, col] .= ...` instead.
│   caller = top-level scope at REPL[8]:1
└ @ Core REPL[8]:1
3-element Vector{Int64}:
 10
 10
 10

As you can see in the warning message this inconsistency (that was known and
discussed for some time already) will be fixed in DataFrames.jl 1.4.
We have been waiting with this change for several releases in order to clearly
inform users about this fix in advance.

This change is not only about consistency, but also to make sure we do not
perform accidental conversions where users most likely do not expect them:

julia> df2 = DataFrame(x = 'a':'c')
3×1 DataFrame
 Row │ x
     │ Char
─────┼──────
   1 │ a
   2 │ b
   3 │ c

julia> df2.x .= 104
┌ Warning: In the 1.4 release of DataFrames.jl this operation will allocate a new column instead of performing an in-place assignment. To perform an in-place assignment use `df[:, col] .= ...` instead.
│   caller = top-level scope at REPL[18]:1
└ @ Core REPL[18]:1
3-element Vector{Char}:
 'h': ASCII/Unicode U+0068 (category Ll: Letter, lowercase)
 'h': ASCII/Unicode U+0068 (category Ll: Letter, lowercase)
 'h': ASCII/Unicode U+0068 (category Ll: Letter, lowercase)

julia> df2
3×1 DataFrame
 Row │ x
     │ Char
─────┼──────
   1 │ h
   2 │ h
   3 │ h

In the future, when DataFrames.jl 1.4 is released, instead you will get a data
frame with column :x having element type Int and storing three 104
values.

Conclusions

The changes I have described today are not something that a new person starts to
use on the first day of working with DataFrames.jl. However, after one learns
the basics more and more advanced queries are needed in practice. Improvements
in functionality and consistency of the design of the core of indexing
mechanisms in DataFrames.jl are hopefully going to make these complex
requirements easier to meet.

The Future of Machine Learning and why it looks a lot like Julia

By: Logan Kilpatrick

Re-posted from: https://towardsdatascience.com/the-future-of-machine-learning-and-why-it-looks-a-lot-like-julia-a0e26b51f6a6?source=rss-2c8aac9051d3------2

Everything you need to know about the deep learning and machine learning ecosystem in Julia.