Author/s: Ramiro Checa-Garcia
Language: es | Translations:
computing
ocamllinear-algebraowl

Ha llegado el momento de intentar usar dune. Lo primero es verificar que esta instalado, por ejemplo, usando opam.

> opam list -i | grep dune

Sino esta instalado dune podemos hacer

> opam install dune

Creando un projecto

En OCaml dune es una herramienta para crear librerías y aplicaciones de modo sistemático e integrado con opam. Para crear un proyecto llamado ocaml4 utilizamos la opción init:

> dune init project ocaml4

lo que va a crear un directorio llamado ocaml4 y la estructura de archivos

Archivo Tipo Función
bin directorio código y reglas para crear un ejecutable
_build directorio aquí se crearan ejecutables y archivos intermedios
dune-project archivo archivo con sintaxis tipo Lisp con información del projecto
lib directorio código y reglas para crear una librería
ocaml4.opam archivo archivo que se completara con info en dune-project para opam
test directorio código y reglas para realizar tests

si miramos en bin hay un archivo main.ml con el típico hello world. Podemos incluir información en dune-project del tipo autores, licencias o descripción, pero no es necesario para las siguientes operaciones.

Creando y usando un executable

> dune build

Esto crea un ejecutable en _build/install/default/bin/main.exe que si ejecutamos

./_build/install/default/bin/main.exe

nos da como salida:

Hello, World!

ya que el código en bin/main.ml es

let () = print_endline "Hello, World!"

Este ejecutable se ha creado cuando hemos usado dune build con información en bin/dune

Archivo Tipo Función
bin/main.ml source ocaml código
bin/dune archivo texto archivo con sintaxis tipo Lisp con reglas para compilar

Compilando con Owl

En esta cuarta sesión de OCaml voy a usar la librería científica llamada Owl,

let x  = Owl.Mat.of_array [|0.0;1.0;2.0;3.0|] 2 2
let () = print_endline "Hello, World!"

Si cambiamos main.ml por el código anterior da un error bin/main.ml, que es Error: Unbound module Owl, por tanto hemos de incluirlo en el archivo dune

(executable
 (public_name ocaml4)
 (name main)
 (libraries ocaml4 owl))

el nombre es owl y no Owl, esto lo podemos ver usando ocamlfind list | grep owl. Si intentamos compilar con dune build tenemos otro error Error (warning 32 [unused-value-declaration]): unused value x., que sin embargo no aparecería si compilara como hemos visto en sesiones previas usando las herramientas manuales (ocamlfind con ocamlopt/ocamlc). Esto se debe a que por defecto dune utiliza flags para el compilado que dan como errores y no warnings determinadas prácticas como no usar una variable definida. En la siguiente porción de código incluimos lo siguiente

(executable
 (public_name ocaml4)
 (name main)
 (libraries ocaml4 owl))
 (flags (:standard -warn-error "-unused-value-declaration")))

Ahora la compilación con dune build da un warning (y no un error) Warning 32 [unused-value-declaration]: unused value x.

Aprendiendo Owl

Ahora voy a incluir código usando Owl sin ningún objetivo específico, únicamente para aprender a usar algunas funciones básicas.

open Owl

let x  = Owl.Mat.of_array [|0.0;1.0;2.0;3.0|] 2 2
let x1 = Owl.Mat.uniform 2 2 
let y  = Owl.Mat.of_array [|0.;1.;2.;3.|] 4 1
let z  = Owl.Mat.of_array [|0.;1.;2.;3.|] 1 4
let x2 = Mat.(x + x1)
let x3 = Mat.(x *@ x1)
let x4 = Mat.(y *@ z)
let x5 = Mat.(z *@ y)

let () = print_endline "Aprendiendo OWL & OCAML...";
         print_endline "x :"; flush_all() ; 
         Mat.(x |> print)   ; flush_all() ;
         print_endline "y :";
         Mat.(y |> print)   ; flush_all();
         print_endline "z :";
         Mat.(z |> print)   ; flush_all();
         Mat.(x1 |> print)  ; flush_all();
         Mat.(x2 |> print)  ; flush_all();
         Mat.(x3 |> print)  ; flush_all();
         Mat.(x4 |> print)  ; flush_all();
         Mat.(x5 |> print)  ; flush_all();

En el código previo hemos usado open Owl, por tanto en let x2 = Mat.(x + 1) es posible, en todo caso como estamos compilando con la librería owl, podemos usarla sin open pero esto implica la sintaxis let x2 = Owl.Mat.(x + 1).

Ejercicio: Cambio de base

Más que empezar a revisar una a una las funciones disponibles, voy a intentar como siempre, hacer un ejercicio. En este caso voy a tener un conjunto de vectores en una base concreta, y voy a calcular la matriz de cambio de base a otra base diferente y la forma que adquieren los vectores en la segunda base.

Lo primero es recordar algo de álgebra lineal. Una de las estructuras más importantes es un espacio vectorial, que esencialmente son objetos que podemos sumar y escalar. Los números reales son un caso muy simple ya que cualquier suma de números reales es un número real, y multiplicar un número real por otro es también un número real. Si tomamos el producto cartesiano $\ℝ^2=\ℝ$x$\ℝ$, sus elementos son pares de la forma $(a,b)$ con $a,b\∈\ℝ$. Podemos ver que en $\ℝ^2$ tenemos una suma y un producto por un escalar:

  • $(a,b)+(c,d) = (a+c,b+c)$
  • $\α (a,b)$ = $(\αa, \αb)$

Estas operaciones cumplen los requisitos para tener un espacio vectorial, y la dimensión de este espacio vectorial es 2, y podemos escribir cada elemento $x\∈\ℝ^2$ como $x=\α e_1 + \β e_2$, donde $e_1,e_2\∈\ℝ^2$ y son ortogonales. El conjunto ${e_1,e_2}$ es una base del espacio vectorial. Veamos un ejemplo:

Si $x=(4,3)$ y $e_1 = (1,0)$ and $e_2=(0,1)$ entonces $x=4 e_1 + 3 e_2$. Bien, también podemos observar que si tomamos como base $v_1 = (4,0)$ and $v_2=(0,-3)$, ahora mi vector es $x= v_1 + (-1)v_2$. Una vez tenemos una base podemos describir cada vector por sus coordenadas que serian los factores $\α,\β$ en $x=\α e_1 + \β e_2$, pero estas coordenadas dependen como vemos de como elegimos nuestra base. El ejercicio es obtener una matriz que dadas dos base concretas me permita transformar las coordenadas en una base en las coordenadas en otra base.

El algoritmo es sencillo, únicamente tenemos que escribir los vectores de una base en los vectores de la otra base, veamos: $x= v_1 + (-1.0) v_2$, y observamos que en este caso es sencillo: $v_1 = 4 e_1$ y $v_2=-3 e_2$, y $x= v_1 + (-1.0) v_2$ y por tanto $x= 4 e_1 + (-1.0)(-3.0) e_2$, como esperábamos. Vamos a hacerlo en OCaml

let e1 = [| 1.0;  0.0 |]
let e2 = [| 0.0;  1.0 |]
let v1 = [| 4.0;  0.0 |]
let v2 = [| 0.0; -3.0 |]

(* Ponemos las coordenadas de v1 y v2 en la base (e1, e2) en
   forma de matriz, debemos ver que las columnas de la matriz
   resultante son las coordenadas de ambos vectores. Lo llamamos
   ev ya que dadas las coordenadas en base v={v1,v2} me da las
   coordenadas en e = {e1,e2} *)

let ev = Owl.Mat.of_array [| 4.0;  0.0; 0.0; -3.0 |] 2 2

(* Creamos un vector columna que tenga las coordenadas de x 
   en la base {v1, v2}. xv son las coordenadas de x en base 
   {v1, v2} como vector columna *)

let xv = Owl.Mat.of_array [| 1.0; -1.0 |] 2 1

(* Multiplicamos ev y xv, producto de matrices y debemos
   obtener un vector columna con las coordenadas de x 
   en la base {e1, e2} *)

let c  = Owl.Mat.(ev *@ xv)

let () = print_endline "Aprendiendo OWL & OCAML...";
         Owl.Mat.(ev |> print)   ; flush_all() ;
         Owl.Mat.(xv |> print)   ; flush_all() ;
         Owl.Mat.(c  |> print)   ; flush_all() ;

La salida es

Aprendiendo OWL & OCAML...

   C0 C1
R0  4  0
R1  0 -3

   C0
R0  1 
R1 -1

   C0 
R0  4 
R1  3

Esto es ilustrativo pero no muy útil ya que tenemos que introducir manualmente los elementos de la matriz de cambio de base, y ha sido fácil por la simplificación introducida al tener una base muy sencilla ${e_1, e_2}$. Una manera de automatizarlo, es referir todo a esta base que llamaremos base canónica de modo que un cambio de base arbitrario puede determinarse como el cambio de base inicial a la canónica y de esta a la base final. Lo dejo como ejercicio que resolveré en la siguiente sesión.