Vector operations: Coefficient-wise , array computing#

Coefficient-wise operations, like the vector sum (which operate component by component), are very common and useful. For instance, the whole numpy library is based on that (https://numpy.org/doc/stable/user/basics.broadcasting.html). In c++ you can also do it, and it simplifies a lot many operations related to vectors. You have two options: using the standard library valarray, or using Eigen array.

This is, for instance, what you would like to have when adding two structures representing vectors:

Image Description
From: "http://jalammar.github.io/images/numpy/numpy-arrays-adding-1.png"

or with more operations

Image Description
From: "http://jalammar.github.io/images/numpy/numpy-array-subtract-multiply-divide.png"

or with scalars,

Image Description
From: "https://miro.medium.com/v2/resize:fit:1400/format:webp/1*XbZqTBXZmnQtVZ_llbGJTw.png"

Function evaluation#

Furthermore , mathematical function evaluated in this array computing approach means evaluating the function on each component, such as

(20)#\[\begin{equation} \vec y = \sin (\vec x) \end{equation}\]

means

(21)#\[\begin{equation} y_i = \sin x_i. \end{equation}\]

C++ valarray#

You can find the reference implementation at https://en.cppreference.com/w/cpp/numeric/valarray .

#include <iostream>
#include <valarray>
#include <cmath>

int main()
{
    std::valarray<int> v = {1,2,3,4,5,6,7,8,9,10};

    double suma = v.sum();

    std::cout << suma << "\n";

    return 0;
}

Example: Using apply#

Cómo usar la utilidad apply para multiplicar todos los elementos por 2 si el numero es impar y por 3 si el numero es par? Piense en la solución usando for e if y luego puede mirar a https://en.cppreference.com/w/cpp/numeric/valarray/apply

Indexing#

A simple example for indexing, is the following (https://en.cppreference.com/w/cpp/numeric/valarray/operator%3D)

#include <iomanip>
#include <iostream>
#include <valarray>

void print(const char* rem, const std::valarray<int>& v)
{
    std::cout << std::left << std::setw(36) << rem << std::right;
    for (int n: v) std::cout << std::setw(3) << n;
    std::cout << '\n';
}

int main()
{
    std::valarray<int> v1(3);
    v1 = -1; // (3) from a scalar
    print("assigned from scalar: ", v1);

    v1 = {1, 2, 3, 4, 5, 6}; // (8): from initializer list of different size
    print("assigned from initializer_list:", v1);

    std::valarray<int> v2(3);
    v2 = v1[std::slice(0,3,2)]; // (4): from slice array
    print("every 2nd element starting at pos 0:", v2);

    v2 = v1[v1 % 2 == 0]; // (6): from mask array
    print("values that are even:", v2);

    std::valarray<std::size_t> idx = {0,1,2,4}; // index array
    v2.resize(4); // sizes must match when assigning from gen subscript
    v2 = v1[idx]; // (7): from indirect array
    print("values at positions 0, 1, 2, 4:", v2);

    return 0;
}

Masks#

As you can see, all operations are done at the element level. You can also define masks:

#include <iostream>
#include <valarray>

int main()
{
  std::valarray<int> data = {0,1,2,3,4,5,6,7,8,9};

  std::cout << "Initial valarray: ";
  for(int n: data) std::cout << n << ' ';
  std::cout << '\n';

  data[data > 5] = -1; // valarray<bool> overload of operator[]
  // the type of data>5 is std::valarray<bool>
  // the type of data[data>5] is std::mask_array<int>

  std::cout << "After v[v>5]=-1:  ";
  for(std::size_t n = 0; n < data.size(); ++n)
    std::cout << data[n] << ' ';  // regular operator[]
  std::cout << '\n';
}

There is also a nice utility, apply, that you can use to apply a function to each element:

#include <iostream>
#include <valarray>
#include <cmath>

int main()
{
    std::valarray<int> v = {1,2,3,4,5,6,7,8,9,10};
    v = v.apply([](int n)->int {
                    return std::round(std::tgamma(n+1));
                });
    for(auto n : v) {
        std::cout << n << ' ';
    }
    std::cout << '\n';
}

Exercises#

Dot product#

Create a program that computes the dot product among two math vectors using valarray. Your program must read the vector size from the command line. Fill them using any function you want.

Heap or stack?#

Is a valarray in the heap or the stack? Verify (check https://en.cppreference.com/w/cpp/numeric/valarray/resize)

Filter data#

Using the overloaded operators create a valarray for N=1000 points between 0 and \(2\pi\), then compute the \(\sin\) of those numbers and assing the result to a another valarray, and finally print only the values that fulfill that \(|\sin x| \le 0.5\). All of this without using loops.

Simulation#

If you discretize time in steps of size \(\delta t\), and you have the forces on a given ideal particle, you can compute the next position using the Euler algorithm,

(22)#\[\begin{align} \vec R(t+\delta t) &= \vec R(t) + \delta t \vec V(t),\\ \vec V(t + \delta t) &= \vec V(t) + \delta t \vec F(t)/m.\\ \end{align}\]

Use valarrays to model the vectors. Compute the trayectory of a particle of mass \(m=0.987\), with initial conditions \(\vec R(0) = (0, 0, 4.3), V(0) = (0.123, 0.0, 0.98)\), under the influence of gravity. Plot it. Later add some damping. Later add some interaction with the ground.

Extract odd values#

Fill an std::valarray with random integers numbers uniformly distributed in [1000, 2500], and then extract the even values and compute their mean.

Cross product#

Implement a function to compute the cross product between two 3d vectors. Using it, implement a function to compute the angle between the vectors. Remember that

(23)#\[\begin{equation} |\vec A \times \vec B| = A B \sin\theta. \end{equation}\]

Check test cases like \(\vec A \times \vec A = \vec 0, \hat i \times \hat j = \hat k, \ldots\)

Perpendicular vector#

Use the previous cross product function to find a unit vector perpendicular to two vectors.

Plane equation#

Find the equation of the plane in the form \(ax + by + cz + d = 0\), saving the result in a 4d std::vector, given three 3D points represented using std::valarray.

Vector product#

Write a function that returns the vector product

(24)#\[\begin{equation} \vec A \cdot (\vec B \times \vec C) \end{equation}\]

Classical angular momentum and centripetal acceleration#

Given a mass \(m\), a position \(\vec r\), and a velocity \(\vec v = \omega \times \vec r \), compute both the angular momentum \(\vec L = m \vec r \times (\vec r \times \vec \omega)\) and the centripetal acceleration \(\vec a = \vec \omega \times (\vec \omega \times \vec r)\), using two different functions

Other libraries#

Eigen arrays: coefficient wise computation#

This library also implements element-wise operations and data structs, https://eigen.tuxfamily.org/dox/group__TutorialArrayClass.html , https://eigen.tuxfamily.org/dox/group__QuickRefPage.html#title6 . There are many overloaded operations that you can use. Plase check the docs.

This is an example of a sum, element-wise

#include <eigen3/Eigen/Dense>
#include <iostream>

int main()
{
  Eigen::ArrayXXf a(3,3);
  Eigen::ArrayXXf b(3,3);
  a << 1,2,3,
       4,5,6,
       7,8,9;
  b << 1,2,3,
       1,2,3,
       1,2,3;

  // Adding two arrays
  std::cout << "a + b = " << std::endl << a + b << std::endl << std::endl;

  // Subtracting a scalar from an array
  std::cout << "a - 2 = " << std::endl << a - 2 << std::endl;
}

and this an example for vector product

#include <eigen3/Eigen/Dense>
#include <iostream>

int main()
{
  Eigen::ArrayXXf a(2,2);
  Eigen::ArrayXXf b(2,2);
  a << 1,2,
       3,4;
  b << 5,6,
       7,8;
  std::cout << "a * b = " << std::endl << a * b << std::endl;
}

More operations:

#include <eigen3/Eigen/Dense>
#include <iostream>

int main()
{
  Eigen::ArrayXf a = Eigen::ArrayXf::Random(5);
  a *= 2;
  std::cout << "a =" << std::endl
            << a << std::endl;
  std::cout << "a.abs() =" << std::endl
            << a.abs() << std::endl;
  std::cout << "a.abs().sqrt() =" << std::endl
            << a.abs().sqrt() << std::endl;
  std::cout << "a.min(a.abs().sqrt()) =" << std::endl
            << a.min(a.abs().sqrt()) << std::endl;
}