Integrating Python with Elixir Using Erlport
Published: Dec 30 2023
Introduction
Elixir is a great language with some amazing libraries but once in a while you encounter a problem that can be solved easier or better in Python (often because the size of the Python community is so much larger). I recently come across such an issue when I needed to solve an optimization problem and couldn’t find any solvers in Elixir that was easy to work with and well-maintained.
The problem just took a few minutes to solve in Python however so I decided that I will just call Python from Elixir instead of trying to bring the solution into Elixir.
There are a few different ways we can call Python from Elixir but one of the most used and trusted ways is to use an Erlang library called Erlport. Erlport provides the capability to call Python functions from Elixir by sending messages from your process inside the BEAM to a process running Python.
This is particularly useful when you have a Python script that you wish to integrate with your Elixir application. This blog post demonstrates how to use Erlport to communicate with a Python program. We’ll use a Python script that solves an optimization problem using the PuLP library.
Our problem
Let’s take a hypothetical problem to solve. We have a factory that produces two types of products, A and B. Product A requires 2 hours of machine time and 3 hours of labor. Product B requires 1 hour of machine time and 2 hours of labor. The profit from each Product A is $50, and from Product B is $40. The goal is to maximize the weekly profit given a certain number of machine hours and labour hours that we can provide.
Python Script Overview
from pulp import *
def maximize_profit(machine_time, labor_time):
# Define the problem
prob = LpProblem("Maximize Profit", LpMaximize)
# Decision variables
A = LpVariable("Product_A", 0) # Product A units
B = LpVariable("Product_B", 0) # Product B units
# Objective function
prob += 50 * A + 40 * B, "Total Profit"
# Constraints
prob += 1 * A + 2 * B <= machine_time, "Machine Time"
prob += 3 * A + 2 * B <= labor_time, "Labor Time"
# Solve the problem
prob.solve()
# Output the results
return A.varValue, B.varValue, value(prob.objective)
Let’s take a hypothetical problem to solve. We have a factory that produces two types of products, A and B. Product A requires 1 hour of machine time and 3 hours of labor. Product B requires 2 hours of machine time and 2 hours of labor. The profit from each Product A is $50, and from Product B is $40. The goal is to maximize the weekly profit given a certain number of machine hours and labour hours that we can provide.
The Python script maximize_profit
employs the PuLP library for solving a linear programming problem. It aims to maximize weekly profit in a factory that produces two types of products, A and B, considering constraints on machine time and labor hours.
Setting Up Erlport in Elixir
First, add Erlport to your mix.exs
file:
defp deps do
[
{:erlport, "~> 0.10"}
]
end
Then run mix deps.get
to fetch the dependency.
Python Environment
Ensure your Python environment is set up and that PuLP is installed. Erlport utilizes the Python interpreter in your Elixir application’s environment.
Communicating with Python
To invoke the Python function, start a Python interpreter and load the script in Elixir. For a script named optimizer.py
, here’s how:
defmodule ProfitOptimizer do
use GenServer
def start_link(_) do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end
def init(:ok) do
{:ok, python} = :python.start_link(python_path: "python3")
:python.call(python, :erlport.erlterms, :decode, [File.read!("profit_optimizer.py")])
{:ok, python}
end
def maximize_profit(machine_hours, labor_hours) do
GenServer.call(__MODULE__, {:maximize_profit, machine_hours, labor_hours})
end
def handle_call({:maximize_profit, machine_hours, labor_hours}, _from, python) do
args = [machine_hours, labor_hours]
result = :python.call(python, :profit_optimizer, :maximize_profit, args)
{:reply, result, python}
end
end
Explanation
ProfitOptimizer
, aGenServer
, is created for Python interaction.init/1
initializes the Python interpreter and loads the script.maximize_profit/2
serves as a public interface to the Python script.handle_call/3
executes the Python function with required arguments.
Conclusion
Integrating Python scripts into Elixir applications with Erlport unlocks Python’s vast library ecosystem. This guide provides a basic framework for such integration, applicable in various scenarios. Experimentation and exploration are key to effectively leveraging Python within your Elixir projects.