Aquí están mis argumentos sobre por qué la programación funcional puede y debe utilizarse para la ciencia computacional. Los beneficios son enormes, y las desventajas están desapareciendo rápidamente. En mi mente solo hay una estafa:
Con : falta de soporte de lenguaje en C / C ++ / Fortran
Al menos en C ++, esta estafa está desapareciendo, ya que C ++ 14/17 ha agregado potentes funciones para admitir la programación funcional. Es posible que deba escribir un código de biblioteca / soporte usted mismo, pero el idioma será su amigo. Como ejemplo, aquí hay una biblioteca (advertencia: plug) que hace arreglos multidimensionales inmutables en C ++: https://github.com/jzrake/ndarray-v2 .
Además, aquí hay un enlace a un buen libro sobre programación funcional en C ++, aunque no está enfocado en aplicaciones científicas.
Aquí está mi resumen de lo que creo que son los profesionales:
Pros :
- Exactitud
- Comprensibilidad
- Actuación
En términos de corrección , los programas funcionales están claramente bien planteados : te obligan a definir adecuadamente el estado mínimo de tus variables físicas y la función que avanza ese estado hacia adelante en el tiempo:
int main()
{
auto state = initial_condition();
while (should_continue(state))
{
state = advance(state);
side_effects(state);
}
return 0;
}
Resolver una ecuación diferencial parcial (u ODE) es perfecta para la programación funcional; solo está aplicando una función pura ( advance
) a la solución actual para generar la siguiente.
En mi experiencia, el software de simulación física es, en general, cargado por una mala gestión del estado . Por lo general, cada etapa del algoritmo opera en alguna parte de un estado compartido (efectivamente global). Esto hace que sea difícil, o incluso imposible, garantizar el orden correcto de las operaciones, dejando el software vulnerable a errores que pueden manifestarse como seg-faults, o peor, términos de error que no bloquean su código pero comprometen silenciosamente la integridad de su ciencia. salida. Intentar administrar el estado compartido en una simulación física también inhibe el subprocesamiento múltiple, lo cual es un problema para el futuro, ya que las supercomputadoras se están moviendo hacia conteos de núcleos más altos, y la ampliación con MPI a menudo supera las ~ 100k tareas. En contraste, la programación funcional hace que el paralelismo de memoria compartida sea trivial, debido a la inmutabilidad.
El rendimiento también mejora en la programación funcional debido a la evaluación perezosa de los algoritmos (en C ++, esto significa generar muchos tipos en tiempo de compilación, a menudo uno para cada aplicación de una función). Pero reduce la sobrecarga de accesos y asignaciones de memoria, así como elimina el despacho virtual, lo que permite que el compilador optimice un algoritmo completo al ver a la vez todos los objetos de función que lo componen. En la práctica, experimentará con diferentes arreglos de los puntos de evaluación (donde el resultado del algoritmo se almacena en caché en un búfer de memoria) para optimizar el uso de la CPU frente a las asignaciones de memoria. Esto es bastante fácil debido a la alta localidad (vea el ejemplo a continuación) de las etapas del algoritmo en comparación con lo que normalmente verá en un módulo o código basado en la clase.
Los programas funcionales son más fáciles de entender en la medida en que trivializan el estado físico. ¡Eso no quiere decir que su sintaxis sea fácilmente comprensible por todos sus colegas! Los autores deben tener cuidado de usar funciones bien nombradas, y los investigadores en general deben acostumbrarse a ver algoritmos expresados funcionalmente en lugar de procedimientos. Admito que la ausencia de estructuras de control puede ser desagradable para algunos, pero no creo que eso nos impida avanzar hacia el futuro para poder hacer ciencia de mejor calidad en las computadoras.
A continuación se muestra una advance
función de ejemplo , adaptada de un código de volumen finito utilizando el ndarray-v2
paquete. Tenga en cuenta los to_shared
operadores: estos son los puntos de evaluación a los que me refería anteriormente.
auto advance(const solution_state_t& state)
{
auto dt = determine_time_step_size(state);
auto du = state.u
| divide(state.vertices | volume_from_vertices)
| nd::map(recover_primitive)
| extrapolate_boundary_on_axis(0)
| nd::to_shared()
| compute_intercell_flux(0)
| nd::to_shared()
| nd::difference_on_axis(0)
| nd::multiply(-dt * mara::make_area(1.0));
return solution_state_t {
state.time + dt,
state.iteration + 1,
state.vertices,
state.u + du | nd::to_shared() };
}