¿Pueden los buenos sistemas de tipos distinguir entre matrices en diferentes bases?


Mi programa (Hartree-Fock/iterative SCF) tiene dos matrices F y F' que son realmente la misma matriz expresada en dos bases diferentes. Acabo de perder tres horas de tiempo de depuración porque accidentalmente usé F ' en lugar de F. En C++, el comprobador de tipos no captura este tipo de error porque ambas variables son objetos Eigen::Matrix<double, 2, 2>.

Me preguntaba, para el Haskell / ML / etc. gente, si si estuviera escribiendo este programa habría construido un sistema de tipos donde F y F' tenían diferentes tipos? ¿Cómo sería eso? Básicamente estoy tratando de tener una idea de cómo puedo externalizar algunos errores de lógica en el comprobador de tipos.

Editar: La base de una matriz es como la unidad. Se puede decir 1L o sin embargo muchos galones, ambos significan lo mismo. O, para dar un ejemplo de vector, puede decir (0,1) en coordenadas cartesianas o (1,pi/2) en polar. Pero aunque el significado es el mismo, los valores numéricos son diferentes.

Editar: Tal vez unidades fue la analogía equivocada. No estoy buscando algún tipo de registro donde pueda especificar que el primer campo será litros y el segundo galones, sino más bien una manera de decir que esta matriz como un todo, se define en términos de alguna otra matriz (la base), donde la base podría ser cualquier matriz de las mismas dimensiones. Por ejemplo, el constructor se vería algo así como mkMatrix [[1, 2], [3, 4]] [[5, 6], [7, 8]] y luego agregar ese objeto a otra matriz solo verificaría si ambos objetos tenían la misma matriz que sus segundos parámetros. Hace eso sentido?

Editar: definición en Wikipedia, ejemplos trabajados

Author: Wang, 2011-05-01

5 answers

Esta es una muy buena pregunta. No creo que pueda codificar la noción de una base en la mayoría de los sistemas de tipos, porque esencialmente cualquier cosa que el comprobador de tipos necesita ser capaz de terminar, y hacer juicios sobre si dos vectores de valor real son iguales es demasiado difícil. Usted podría tener (2 v_1) + (2 v_2) o 2 (v_1 + v_2), por ejemplo. Hay algunos lenguajes que usan tipos dependientes [ wikipedia ], pero estos son relativamente académicos.

Creo que la mayoría de su dolor de depuración se aliviaría si simplemente codificara las bases en las que funciona la matriz junto con la matriz. Por ejemplo,

newtype Matrix = Matrix { transform :: [[Double]], 
    srcbasis :: [Double], dstbasis :: [Double] }

Y M de la base un a b con N, comprobar que N es de b a c, y devolver una matriz con base un a c.

NOTA seems parece que la mayoría de la gente aquí tiene programación en lugar de fondo de matemáticas, así que voy a proporcionar una breve explicación aqui. Las matrices son codificaciones de transformaciones lineales entre espacios vectoriales. Por ejemplo, si está codificando una rotación de 45 grados en R^2(reales de 2 dimensiones), entonces la forma estándar de codificar esto en una matriz es decir que el vector base estándar e_1, escrito "[1, 0]", se envía a una combinación de e_1 y e_2, a saber [1/sqrt(2), 1/sqrt (2)]. El punto es que puede codificar la misma rotación diciendo a dónde van los diferentes vectores, por ejemplo, podría decir dónde está enviando [1,1] and [1,-1] instead of e_1=[1,0] and e_2=[0,1], and this would have a different matrix representation.

Editar 1

Si tienes un conjunto finito de bases con las que estás trabajando, puedes hacerlo...

{-# LANGUAGE EmptyDataDecls #-}
data BasisA
data BasisB
data BasisC

newtype Matrix a b = Matrix { coefficients :: [[Double]] }

multiply :: Matrix a b -> Matrix b c -> Matrix a c
multiply (Matrix a_coeff) (Matrix b_coeff) = (Matrix multiplied) :: Matrix a c
    where multiplied = undefined -- your algorithm here

Luego, en ghci (el intérprete interactivo de Haskell),

*Matrix> let m = Matrix [[1, 2], [3, 4]] :: Matrix BasisA BasisB
*Matrix> m `multiply` m

<interactive>:1:13:
    Couldn't match expected type `BasisB'
        against inferred type `BasisA'
*Matrix> let m2 = Matrix [[1, 2], [3, 4]] :: Matrix BasisB BasisC
*Matrix> m `multiply` m2
-- works after you finish defining show and the multiplication algorithm
 16
Author: gatoatigrado,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-01 22:50:16

Esto es completamente posible en Haskell.

Dimensiones verificadas estáticamente

Haskell tiene matrices con dimensiones verificadas estáticamente, donde las dimensiones se pueden manipular y comprobar estáticamente, evitando la indexación en la dimensión incorrecta. Algunos ejemplos:

Esto solo funcionará en matrices 2-D :

multiplyMM :: Array DIM2 Double -> Array DIM2 Double -> Array DIM2 Double

Un ejemplo de repa debería darte un sentido. Aquí, tomar una diagonal requiere una matriz 2D, devuelve una 1D matriz del mismo tipo.

diagonal :: Array DIM2 e -> Array DIM1 e

O, de El tutorial repa de Matt sottile , dimensiones verificadas estáticamente en una transformada de matriz 3D:

f :: Array DIM3 Double -> Array DIM2 Double
f u =
  let slabX = (Z:.All:.All:.(0::Int))
      slabY = (Z:.All:.All:.(1::Int))
      u' = (slice u slabX) * (slice u slabX) +
           (slice u slabY) * (slice u slabY)
  in
    R.map sqrt u'

Unidades estáticamente verificadas

Otro ejemplo fuera de la programación matricial: estáticamente comprobado unidades de dimensión, por lo que es un error de tipo para confundir, por ejemplo, pies y metros, sin hacer la conversión.

 Prelude> 3 *~ foot + 1 *~ metre
 1.9144 m

O para un conjunto completo de unidades y cuantificaciones del SI.

Por ejemplo, no se pueden agregar cosas de diferente dimensión, como volúmenes y longitudes:

> 1 *~ centi litre + 2 *~ inch 
Error:
Expected type: Unit DVolume a1
  Actual type: Unit DLength a0

Por lo tanto, siguiendo los tipos de dimensión de matriz de estilo repa , sugeriría agregar un parámetro de tipo fantasma Base a su tipo de matriz, y usarlo para distinguir entre bases. En Haskell, el índice Dim type argumento da el rango de la matriz (es decir, su forma), y usted podría hacer lo mismo.

O, si por base te refieres a alguna dimensión en las unidades, usando dimensional tipo.

Así que, sí, esto es casi una técnica de productos básicos en Haskell ahora, y hay algunos ejemplos de diseño con tipos como este para ayudarle a empezar.

 19
Author: Don Stewart,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-01 18:51:10

Aunque me doy cuenta de que esto no aborda estrictamente la pregunta (aclarada) – mis disculpas – parece relevante al menos en relación con la respuesta popular de Don Stewart...

Soy el autor de la biblioteca Haskell dimensional que Don hizo referencia y proporcionó ejemplos. También he estado escribiendo-algo bajo el radar - una biblioteca experimental rudimentaria álgebra lineal basada en dimensional . Esta biblioteca de álgebra lineal rastrea estáticamente los tamaños de vectores y matrices, así como las dimensiones físicas ("unidades") de sus elementos sobre una base por elemento.

Este último punto – el seguimiento de las dimensiones físicas sobre una base por elemento – es bastante desafiante y quizás excesivo para la mayoría de los usos, e incluso se podría argumentar que tiene poco sentido matemático tener cantidades de diferentes dimensiones físicas como elementos en cualquier vector/matriz dado. Sin embargo, algunas aplicaciones de álgebra lineal de interés para mí, tales como kalman filtrado y ponderado la estimación de mínimos cuadrados generalmente utiliza vectores de estado heterogéneos y matrices de covarianza.

Usando un filtro de Kalman como ejemplo, considere un vector de estado x = [d, v] que tiene dimensiones físicas [L, LT^-1] . El siguiente vector de estado (futuro) es predicho por la multiplicación por la matriz de transición de estado F, es decir: x' = F x_. Claramente para que esta ecuación tenga sentido F no puede ser arbitraria sino que debe tener tamaño y dimensiones físicas [[1, T], [T^-1, 1]]. La función predict_x' debajo estáticamente asegura que esta relación se mantenga:

predict_x' :: (Num a, MatrixVector f x x) => Mat f a -> Vec x a -> Vec x a
predict_x' f x_ = f |*< x_

(El antiestético operador |*< denota la multiplicación de una matriz a la izquierda con un vector a la derecha.)

Más generalmente, para un vector de estado a priori x_ de tamaño arbitrario y con elementos de dimensiones físicas arbitrarias, pasar una matriz de transición de estado f con tamaño" incompatible " y/o dimensiones físicas a predict_x' causará un tiempo de compilación error.

 11
Author: Björn Buckwalter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-02 12:14:20

En F# (que originalmente evolucionó de OCaml), puedes usar unidades de medida. Andrew Kenned, quien diseñó la función (y también creó una teoría muy interesante detrás de ella) tiene una gran serie de artículos que lo demuestran.

Es muy probable que esto se use en su escenario, aunque no entiendo completamente la pregunta. Por ejemplo, puede declarar dos tipos de unidad como este:

[<Measure>] type litre
[<Measure>] type gallon

Agregar litros y galones le da un tiempo de compilación error:

1.0<litre> + 1.0<gallon> // Error!

F # no inserta automáticamente la conversión entre diferentes unidades, pero puede escribir una función de conversión:

let toLitres gal = gal * 3.78541178<litre/gallon>
1.0<litre> + (toLitres 1.0<gallon>)

Lo bueno de las unidades de medida en F# es que se deducen automáticamente y las funciones son genéricas. Si multiplica 1.0<gallon> * 1.0<gallon>, el resultado es 1.0<gallon^2>.

La gente ha utilizado esta función para varias cosas, desde la conversión de medidores virtuales a píxeles de pantalla (en simulaciones del sistema solar) hasta la conversión de monedas (dólares en sistemas financieros). Aunque no soy un experto, es muy probable que pueda usarlo de alguna manera para su dominio del problema también.

 6
Author: Tomas Petricek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-01 20:58:22

Si se expresa en una base diferente, simplemente puede agregar un parámetro de plantilla para que actúe como base. Eso diferenciará esos tipos. A float es a float es a float - si no desea que dos valores float sean los mismos si realmente tienen el mismo valor, entonces debe informar al sistema de tipos al respecto.

 5
Author: Puppy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-01 18:48:52