Як написати розмірно-агностичний код?


19

Мені часто доводиться писати дуже схожий код для однієї, двох і тривимірних версій заданої операції / алгоритму. Підтримання всіх цих версій може стати втомливим. Просте генерування коду працює досить добре, але, здається, існує кращий шлях.

Чи є відносно простий спосіб написати операцію один раз і чи узагальнити її до вищих чи нижчих розмірів?

Один конкретний приклад такий: припустимо, мені потрібно обчислити градієнт поля швидкості в спектральному просторі. У трьох вимірах петлі Фортран виглядали б приблизно так:

do k = 1, n
  do j = 1, n
    do i = 1, n
      phi(i,j,k) = ddx(i)*u(i,j,k) + ddx(j)*v(i,j,k) + ddx(k)*w(i,j,k)
    end do
  end do
end do

де ddxмасив відповідним чином визначений. (Можна також зробити це з матричним множенням.) Код двовимірного потоку майже точно такий же, за винятком того, що третій вимір випадає з циклів, індексів та кількості компонентів. Чи є кращий спосіб висловити це?

Інший приклад: припустимо, у мене тривимірна швидкість визначена швидкістю рідини. Для інтерполяції швидкості до довільного розташування (тобто не відповідного точкам сітки) можна використовувати одновимірний алгоритм Невілля послідовно над усіма трьома вимірами (тобто зменшення розмірів). Чи є простий спосіб зробити зменшення розмірів за умови одновимірної реалізації простого алгоритму?

Відповіді:


13

Ви дивитесь на те, як це робиться deal.II ( http://www.dealii.org/ ) - там незалежність розмірності лежить в самому серці бібліотеки і моделюється як аргумент шаблону для більшості типів даних. Дивіться, наприклад, вимірювально-агностичний вирішувач Laplace у програмі підручника 4-го кроку:

http://www.dealii.org/developer/doxygen/deal.II/step_4.html

Дивись також

https://github.com/dealii/dealii/wiki/Frequently-Asked-Questions#why-use-templates-for-the-space-dimension


Я абсолютно згоден. Я не знайшов кращого підходу, ніж те, що робить Deal.II. Вони використовують шаблони дуже цікаво, щоб подолати цю проблему.
Елділа

1
Хороший ресурс, але досить страхітливий, якщо ви не обмацуєте шаблони C ++.
meawoppl

@Wolfgang Bangerth: Чи визначає deal.ii ітератори за допомогою шаблонів?
Меттью Емметт

@MatthewEmmett: Так.
Вольфганг Бангерт

@meawoppl: Насправді, ні. Я регулярно викладаю заняття на deal.II, і на початку просто кажу студентам, що все, що говорить, що ClassWever <2> знаходиться у 2d, ClassWever <3> - у 3d, а ClassWever <dim> - у dim-d. Я приношу урок по шаблонах де - то в 3 -й тижня, і в той час, ймовірно , що студенти не розуміють , як це працює , перш ніж , що вони повністю функціональні , використовуючи його в будь-якому випадку.
Вольфганг Бангерт

12

Питання підкреслює, що більшість "простих" мов програмування (як мінімум, C, Fortran, принаймні) не дозволяють зробити це чисто. Додатковим обмеженням є те, що ви бажаєте помітної зручності та хорошої роботи.

Тому замість написання коду, що залежить від розміру , розгляньте можливість написання коду, який генерує код, що залежить від розміру. Цей генератор не залежить від розміру, навіть якщо обчислювальний код не є. Іншими словами, ви додаєте шар міркування між своєю нотацією та кодом, що виражає обчислення. Шаблони C ++ - це одне і те ж: Вгору вони вбудовані прямо в мову. Знизу, вони дещо громіздко писати. Це зводить питання до того, як практично реалізувати генератор коду.

OpenCL дозволяє робити генерацію коду під час виконання досить чисто. Це також забезпечує дуже чіткий поділ між «зовнішньою програмою керування» та «внутрішньою петлею / ядром». Зовнішня, генеруюча програма набагато менш обмежена продуктивністю, а тому може бути написана зручною мовою, як Python. Це моя надія на те, як PyOpenCL звикне - вибачте за оновлений безсоромний штекер.


Андреас! Ласкаво просимо до scicomp! Радий, що вас на сайті, я думаю, що ви знаєте, як зв’язатися зі мною, якщо у вас виникнуть запитання.
Арон Ахмадія

2
+10000 для автоматичного генерування коду як рішення цієї проблеми замість магії C ++.
Джефф

9

Це може бути здійснено будь-якою мовою за допомогою наступного грубого ментального прототипу:

  1. Створіть список розширень кожного виміру (щось на зразок shape () в MATLAB, я думаю)
  2. Створіть список свого поточного місцезнаходження у кожному вимірі.
  3. Напишіть цикл для кожного виміру, що містить цикл щодо зміни розміру на основі зовнішнього циклу.

Звідси йдеться про боротьбу з синтаксисом вашої певної мови, щоб зберегти ваш код n-сумісним.

Написавши n-мірний вирішувач динаміки флюїду , я виявив, що корисно мати мову, яка підтримує розпакування списку як об'єкта як аргументи функції. Тобто a = (1,2,3) f (a *) -> f (1,2,3). Додатково просунуті ітератори (такі як нумеровані цифри) роблять код на порядок чистішим.


Синтаксис Python для цього виглядає приємно і лаконічно. Цікаво, чи є приємний спосіб зробити це з Fortran ...
Меттью Емметт

1
Мало болісно мати справу з динамічною пам'яттю у Фортран. Можливо, моя основна скарга на мову.
meawoppl

5

н1×н2×н3нj=1


Отже, щоб не залежати від розміру, ваш код потрібно писати для розмірів maxdim + 1, де maxdim - це максимально можливий розмір, з яким користувач може коли-небудь стикатися. Скажімо, maxdim = 100. Наскільки корисний отриманий код?
Джефф

4

Чіткі відповіді, якщо ви хочете зберегти швидкість Fortran, - це використовувати мову, яка має належне генерування коду, як Julia або C ++. Шаблони C ++ вже були згадані, тому я згадаю тут інструменти Юлії. Генеровані функції Джулії дозволяють використовувати її метапрограмування для побудови функцій на вимогу за допомогою інформації про тип. Тому по суті те, що ти тут можеш зробити, - це зробити

@generated function f(x)
   N = ndims(x)
   quote
     # build the code for the function
   end
end

а потім ви використовуєте Nпрограму для побудови коду, який ви хочете виконати, враховуючи, що він Nрозмірний. Тоді декартові бібліотеки Юлії або пакети, такі як вирази Einsum.jl, можна легко побудувати для Nрозмірної функції.

Приємно, що Julia тут полягає в тому, що ця функція є статично скомпільована та оптимізована для кожного нового розмірного масиву, який ви використовуєте, тому він не буде компілювати більше, ніж вам потрібно, але він отримає швидкість C / Fortran. Зрештою, це схоже на використання шаблонів C ++, але це мова вищого рівня з безліччю інструментів, щоб зробити це простіше (досить просто, що це буде гарною проблемою домашнього завдання для нижнього рівня).

Ще однією мовою, яка корисна для цього, є такий Lisp, як звичайний Lisp. Він простий у використанні, оскільки подібно до Julia, він дає вам складений AST з великою кількістю вбудованих інструментів самоаналізу, але на відміну від Julia, він не буде автоматично його компілювати (у більшості дистрибутивів).


1

Я в тому ж (Фортран) човні. Після того, як у мене є елементи 1D, 2D, 3D і 4D (я роблю проекційну геометрію), я створюю однакові оператори для кожного типу, а потім пишу свою логіку рівняннями високого рівня, які дозволяють зрозуміти, що відбувається. Це не так повільно, як можна подумати, мати окремі петлі кожної операції та багато копії пам'яті. Я дозволяю компілятору / процесору робити оптимізацію.

Наприклад

interface operator (.x.)
    module procedure cross_product_1x2
    module procedure cross_product_2x1
    module procedure cross_product_2x2
    module procedure cross_product_3x3
end interface 

subroutine cross_product_1x2(a,b,c)
    real(dp), intent(in) :: a(1), b(2)
    real(dp), intent(out) :: c(2)

    c = [ -a(1)*b(2), a(1)*b(1) ]
end subroutine

subroutine cross_product_2x1(a,b,c)
    real(dp), intent(in) :: a(2), b(1)
    real(dp), intent(out) :: c(2)

    c = [ a(2)*b(1), -a(1)*b(1) ]
end subroutine

subroutine cross_product_2x2(a,b,c)
    real(dp), intent(in) :: a(2), b(2)
    real(dp), intent(out) :: c(1)

    c = [ a(1)*b(2)-a(2)*b(1) ]
end subroutine

subroutine cross_product_3x3(a,b,c)
    real(dp), intent(in) :: a(3), b(3)
    real(dp), intent(out) :: c(3)

    c = [a(2)*b(3)-a(3)*b(2), a(3)*b(1)-a(1)*b(3), a(1)*b(2)-a(2)*b(1)]
end subroutine

Для використання в рівняннях типу

m = e .x. (r .x. g)  ! m = e×(r×g)

де eі rі gможе мати будь-яку розмірність, яка має математичний сенс.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.