class matrix
{
public:
// ....
private:
size_t m_nb_rows;
size_t m_nb_cols;
std::vector<double> m_data;
};
class matrix
{
public:
// ....
private:
size_t m_nb_rows;
size_t m_nb_cols;
std::vector<double> m_data;
};
std::vector
matrix matrix::sigmoid(const matrix& m)
{
matrix res(m.nb_rows(), m.nb_cols());
std::transform(m.m_data.begin(), m.m_data.end(), res.m_data.begin()
[](double arg) { return 1. / (1. + std::exp(-arg)); });
}
Stack allocation
double m_data[N];
// N must be known during compilation
// N is not mutable, you cannot resize m_data;
Heap allocation
double* p_data = new double[size];
// size can be computed at runtime
delete[] p_data; // Never forget this
delete[] p_data; // Never do it twice on a non null pointer
p_data = nullptr;
delete[] p_data; // OK
>
double* p1 = new double(size);
double* p2 = p1;
delete p1;
p2[2] // crash
The survival guide to dynamic allocation
class uvector
{
public:
uvector(std::size_t size = 0);
uvector(std::size_t size, double value);
private:
double* p_data;
std::size_t m_size;
};
uvector::uvector(std::size_t size)
: p_data(nullptr), m_size(0)
{
if(size != 0)
{
p_data = new double[size];
m_size = size;
}
}
uvector::uvector(std::size_t size, double value)
: uvector(size) // delegating constructor
{
std::fill(p_data, p_data + size, value);
}
class uvector
{
public:
uvector(std::size_t size = 0);
uvector(std::size_t size, double value);
~uvector();
};
uvector::~uvector()
{
delete[] p_data;
p_data = nullptr;
m_size = 0;
}
class uvector
{
public:
uvector(std::size_t size = 0);
uvector(std::size_t size, double value);
~uvector();
uvector(const uvector&);
uvector& operator=(const uvector&);
}
uvector::uvector(const uvector& rhs)
: p_data(nullptr), m_size(0)
{
if(rhs.m_size != 0)
{
p_data = new double[rhs.m_size];
m_size = rhs.m_size;
std::copy(rhs.p_data, rhs.p_data + m_size, p_data);
}
}
uvector& uvector::operator=(const uvector& rhs)
{
delete[] p_data;
p_data = new double[rhs.m_size];
std::copy(rhs.p_data, rhs.p_data + rhs.m_size, p_data);
m_size = rhs.m_size;
return *this;
}
uvector v(2, 2.);
v = v;
Copy and swap idiom
uvector& uvector::operator=(const uvector& rhs)
{
double* tmp = new double[rhs.m_size]; // Always allocate a temporary
std::copy(rhs.p_data, rhs.p_data + rhs.m_size, tmp); // copy
std::swap(tmp, p_data); // then swap
delete[] tmp; // then delete the temporary
m_size = rhs.m_size;
return *this;
}
uvector& uvector::operator=(const uvector& rhs)
{
double* tmp = new double[rhs.m_size]; // Always allocate a temporary
std::copy(rhs.p_data, rhs.p_data + rhs.m_size, tmp); // copy
tmp_size = rhs.m_size; // copy
std::swap(tmp, p_data); // swap
std::swap(tmp_size, m_size); // swap
delete[] tmp; // then delete the temporary
return *this;
}
class uvector
{
public:
void swap(uvector& rhs);
private:
double* p_data;
std::size_t m_size;
};
void swap(uvector& lhs, uvector& rhs);
void uvector::swap(uvector& rhs)
{
using std::swap;
swap(p_data, rhs.p_data);
swap(m_size, rhs.m_size);
}
void swap(uvector& lhs, uvector& rhs)
{
lhs.swap(rhs);
}
Copy and swap idiom
uvector& uvector::operator=(const uvector& rhs)
{
uvector tmp(rhs);
swap(*this, tmp);
return *this;
}
uvector compute(const uvector& param)
{
uvector res;
// do some computation ...
return res;
}
// Inefficient copy
uvector res = compute(my_huge_param);
uvector& compute(const uvector& param)
{
uvector res;
// do some computation ...
return res; // Binding a temporary to a non const reference!
}
// dangling reference
uvector& res = compute(my_huge_param);
uvector compute(const uvector& param)
{
uvector res;
// do some computation ...
return res;
}
// rvalue reference
uvector&& res = compute(my_huge_param);
lvalue
rvalue
uvector& func(uvector& t)
{
// do some stuff
return t;
}
uvector t, something;
func(t) = something;
uvector compute(const huge_param& param);
void function(const uvector& param);
void function(uvector&& param);
uvector m;
function(m);
function(compute(m));
uvector&& ref = compute(m);
function(ref);
uvector m;
function(m); // calls function(const uvector& param);
function(compute(m)); // calls function(uvector&& param);
uvector&& ref = compute(m);
function(ref); // calls function(const uvector& param);
function(static_cast<uvector&&>(ref));
function(std::move(uvector));
class uvector
{
public:
uvector(std::size_t = 0);
uvector(std::size_t size, double value);
~uvector();
uvector(const uvector&)
uvector& operator=(const uvector& rhs);
uvector(uvector&&);
uvector& operator=(uvector&& rhs);
};
uvector::uvector(uvector&& rhs)
: p_data(std::move(p_data)), m_size(std::move(rhs.m_size))
{
}
uvector::uvector(uvector&& rhs)
: p_data(std::move(p_data)), m_size(std::move(rhs.m_size))
{
rhs.p_data = nullptr;
rhs.m_size = 0;
}
uvector& uvector::operator=(uvector&& rhs)
{
using std::swap;
swap(p_data, rhs.p_data);
swap(m_size, rhs.m_size);
return *this;
}
If you need to declare and implement any of
... you probably need to implement all off them
A population of candidate solutions (called individuals or phenotypes) to an optimization problem is evolved toward better solutions. Typical steps are:
Given a list of cities and the distance between each pair of cities, find the shortest route that visits each city exactly once and returns to the original city.
Selection
Write a select method that implements a tournament selection:
Write a mutate method:
Write a crossover method:
Write the evolve method:
where $h = \frac{1}{N}$ and $N$ is a positive integer.
We replace $u$ by a discretization $u_i = u(i/N)$.
Using the class matrix introduced in the previous lecture, create the matrix $A$
Create the vector $x$ where all the entries will be equal to $0$
Create the vector $b$ where all the entries will be equal to $1$
There are many ways to solve this linear system: LU, GMRES, CG, ...
In the following, we will implement the CG (conjugate gradient) algorithm.
The matrix $A$ of the laplacian contains a lot of zero.
Then the matrix-vector product computes a lot of zero.
The CSR format stores only the non-zero elements.