Transformation from MATLAB to PyTorch and EvoX#
This document aims to guide MATLAB users in transitioning to PyTorch and EvoX for evolutionary computation. We will highlight the core differences between MATLAB and PyTorch in terms of syntax, data structures, and workflow. We will then illustrate these differences using a Particle Swarm Optimization (PSO) example in both MATLAB and PyTorch.
Syntax Differences#
Array Creation and Indexing#
MATLAB#
Uses 1-based indexing.
Vectors and matrices are declared using square brackets and semicolons (e.g.,
[1 2 3; 4 5 6]
). Random initialization withrand()
returns values in the interval \([0, 1)\).Slicing is performed using the
(start:end)
syntax and utilizes 1-based indexing.
PyTorch#
Uses 0-based indexing.
Arrays (tensors) are typically created using constructors like
torch.rand()
,torch.zeros()
, or Python lists converted to tensors withtorch.tensor()
.Slicing is done using
[start:end]
with 0-based indices.
Matrix Computation#
MATLAB#
Performs linear algebraic matrix multiplication by
*
.Uses
.*
to multiply corresponding elements of matrices of the same size./
represents the matrix right division..^
represents the element-wise power.Trailing and leading dimension(s) of tensors with length 1 is/are ignored.
Automatically find broadcastable dimensions for element-wise operations and perform implicit dimension extension.
PyTorch#
Performs linear algebraic matrix multiplication by
@
ortorch.matmul()
.Directly uses
*
to multiply corresponding elements of tensors of the same shape or broadcastable shapes./
represents the element-wise division.**
represents the element-wise power.Dimension(s) of tensors with length 1 is/are preserved and treated as broadcast dimension.
Prevent most implicit dimension extension, broadcast dimension(s) are usually required.
Functions and Definitions#
MATLAB#
A function is defined by the
function
keyword.A file can contain multiple functions, but typically the primary function shares the file name.
Anonymous functions (e.g.,
@(x) sum(x.^2)
) are used for short inline calculations.
PyTorch#
Functions are defined using the def keyword, typically within a single
.py
file or module.Classes are used to encapsulate data and methods in an object-oriented manner.
Lambdas serve as short anonymous functions (
lambda x: x.sum()
), but multi-line lambdas are not allowed.
Control Flow#
MATLAB#
Uses for
i = 1:N
…end
loops with 1-based indexing.Conditional statements like
if
,elseif
, andelse
.
PyTorch#
Uses
for i in range(N):
with 0-based indexing.Indentation is significant for scoping in loops and conditionals (no
end
keyword).
Printing and Comments#
MATLAB#
Uses
fprintf()
functions for formatted output.Uses
%
for single-line comments.
PyTorch#
Uses
print
with f-strings for formatted output.Uses
#
for single-line comments.
Multi-line Coding#
MATLAB#
Uses
...
at the trailing of a line to indicate that the next line shall be treated as the same line as.
Python#
Uses
\
at the trailing of a line to indicate that the next line shall be treated as the same line as.If multiple lines are inside parentheses, no specific trailing symbol is required.
How to Write Evolutionary Computation Algorithm via EvoX?#
MATLAB#
A MATLAB code example for PSO algorithm is as follows:
function [] = example_pso()
pso = init_pso(100, [-10, -10], [10, 10], 0.6, 2.5, 0.8);
test_fn = @(x) (sum(x .* x, 2));
for i = 1:20
pso = step_pso(pso, test_fn);
fprintf("Iteration = %d, global best = %f\n", i, pso.global_best_fitness);
end
end
function [self] = init_pso(pop_size, lb, ub, w, phi_p, phi_g)
self = struct();
self.pop_size = pop_size;
self.dim = length(lb);
self.w = w;
self.phi_p = phi_p;
self.phi_g = phi_g;
% setup
range = ub - lb;
population = rand(self.pop_size, self.dim);
population = range .* population + lb;
velocity = rand(self.pop_size, self.dim);
velocity = 2 .* range .* velocity - range;
self.lb = lb;
self.ub = ub;
% mutable
self.population = population;
self.velocity = velocity;
self.local_best_location = population;
self.local_best_fitness = Inf(self.pop_size, 1);
self.global_best_location = population(1, :);
self.global_best_fitness = Inf;
end
function [self] = step_pso(self, evaluate)
% Evaluate
fitness = evaluate(self.population);
% Update the local best
compare = find(self.local_best_fitness > fitness);
self.local_best_location(compare, :) = self.population(compare, :);
self.local_best_fitness(compare) = fitness(compare);
% Update the global best
values = [self.global_best_location; self.population];
keys = [self.global_best_fitness; fitness];
[min_val, min_index] = min(keys);
self.global_best_location = values(min_index, :);
self.global_best_fitness = min_val;
% Update velocity and position
rg = rand(self.pop_size, self.dim);
rp = rand(self.pop_size, self.dim);
velocity = self.w .* self.velocity ...
+ self.phi_p .* rp .* (self.local_best_location - self.population) ...
+ self.phi_g .* rg .* (self.global_best_location - self.population);
population = self.population + velocity;
self.population = min(max(population, self.lb), self.ub);
self.velocity = min(max(velocity, self.lb), self.ub);
end
In MATLAB, function init_pso()
initializes the algorithm, and a separate function step_pso()
performs an iteration step and the main function example_pso()
orchestrates the loop.
EvoX#
In EvoX, you can construct the PSO algorithm in following way:
First, it is recommended to import necessary modules and functions from EvoX and PyTorch.
import torch
from evox.core import *
from evox.utils import *
from evox.workflows import *
from evox.problems.numerical import Sphere
Then, you can transform the MATLAB code to the python code correspondingly according to the “Syntax Differences” section.
def main():
pso = PSO(pop_size=10, lb=torch.tensor([-10.0, -10.0]), ub=torch.tensor([10.0, 10.0]))
wf = StdWorkflow()
wf.setup(algorithm=pso, problem=Sphere())
for i in range(1, 21):
wf.step()
print(f"Iteration = {i}, global best = {wf.algorithm.global_best_fitness}")
@jit_class
class PSO(Algorithm):
def __init__(self, pop_size, lb, ub, w=0.6, phi_p=2.5, phi_g=0.8):
super().__init__()
self.pop_size = pop_size
self.dim = lb.shape[0]
self.w = w
self.phi_p = phi_p
self.phi_g = phi_g
# setup
lb = lb.unsqueeze(0)
ub = ub.unsqueeze(0)
range = ub - lb
population = torch.rand(self.pop_size, self.dim)
population = range * population + lb
velocity = torch.rand(self.pop_size, self.dim)
velocity = 2 * range * velocity - range
self.lb = lb
self.ub = ub
# mutable
self.population = population
self.velocity = velocity
self.local_best_location = population
self.local_best_fitness = torch.full((self.pop_size,), fill_value=torch.inf)
self.global_best_location = population[0, :]
self.global_best_fitness = torch.tensor(torch.inf)
def step(self):
# Evaluate
fitness = self.evaluate(self.population)
# Update the local best
compare = self.local_best_fitness > fitness
self.local_best_location = torch.where(compare.unsqueeze(1), self.population, self.local_best_location)
self.local_best_fitness = torch.where(compare, fitness, self.local_best_fitness)
# Update the global best
values = torch.cat([self.global_best_location.unsqueeze(0), self.population], dim=0)
keys = torch.cat([self.global_best_fitness.unsqueeze(0), fitness], dim=0)
min_index = torch.argmin(keys)
self.global_best_location = values[min_index]
self.global_best_fitness = keys[min_index]
# Update velocity and position
rg = torch.rand(self.pop_size, self.dim)
rp = torch.rand(self.pop_size, self.dim)
velocity = (
self.w * self.velocity
+ self.phi_p * rp * (self.local_best_location - self.population)
+ self.phi_g * rg * (self.global_best_location - self.population)
)
population = self.population + velocity
self.population = clamp(population, self.lb, self.ub)
self.velocity = clamp(velocity, self.lb, self.ub)
# Run the main function
if __name__ == "__main__":
main()
Note
It is worth noting that we use []
with ;
and ,
in MATLAB to concatenate matrices and vectors along specific dimension; however, in EvoX, the torch.cat
must be invoked with argument dim
to indicate the concatenation dimension.
Moreover, in PyTorch, tensors to be concatenated must have the same number of dimensions; therefore, additional XXX.unsqueeze(0)
is applied to add a new dimension of length 1 before the first dimension.
In EvoX, the PSO logic is encapsulated within a class that inherits from Algorithm
. This object-oriented design simplifies state management and iteration, and introduces following advantages:
Inherited
evaluate()
method You can simply callself.evaluate(self.population)
to compute fitness values, rather than manually passing your objective function each iteration.Built-In Workflow Integration When you register your PSO class with a workflow
StdWorkflow
, it handles iterative calls tostep()
on your behalf.
By extending Algorithm
, __init__()
sets up all major PSO components (population, velocity, local/global best, etc.) in a standard Python class constructor.