Stack Overflow на русском Asked by serg.tortilliani on August 30, 2021
Имеется фортрановская библиотека LAPACK – пакет работы с линейной алгеброй. Конкретно в данный момент интересует её функция DGTSV (метод прогонки для трёхдиагональных матриц). Её сигнатура в фортране такая:
subroutine dgtsv (
integer N,
integer NRHS,
double precision, dimension( * ) DL,
double precision, dimension( * ) D,
double precision, dimension( * ) DU,
double precision, dimension( ldb, * ) B,
integer LDB,
integer INFO
)
В компонентном паскале (он только 32-bit, поэтому версия LAPACK – тоже 32-bit) я подключал LAPACK таким образом (звездочка имеет смысл метки для видимости извне, аналог public в C#):
MODULE LinalgLAPACK ["liblapack.dll"];
PROCEDURE DGTSV* ["dgtsv_"] (VAR n : LAPACK_Integer; VAR nrhs : LAPACK_Integer;
VAR dl : Double; VAR d : Double; VAR du : Double; VAR b : Double;
VAR ldb : LAPACK_Integer; VAR info : LAPACK_Integer);
Тут есть нюанс – liblapack.dll требует libblas.dll, а еще libgfortran-3.dll, libgcc_s_dw2-1.dll и libquadmath-0.dll. Все они – 32-битные. Зависимости исследовались Dependency Walker’ом.
Поскольку в фортране ВСЕ аргументы передаются ТОЛЬКО по ссылке, поэтому аргументы-указатели на массивы заменены на ссылки на начальные элементы массивов. В основном модуле вызов выглядел так:
MODULE LinalgTest;
IMPORT LAPACK := LinalgLAPACK;
...
PROCEDURE Test*;
VAR
size, nrhs, info : INTEGER;
c0, c1, c2, v : POINTER TO ARRAY OF REAL;
BEGIN
size := 3;
nrhs := 1;
NEW (c0, size);
NEW (c1, size);
NEW (c2, size);
NEW (v, size);
...
LAPACK.DGTSV (size, nrhs, c0[1], c1[0], c2[0], v[0], size, info);
END Test;
Главная диагональ имеет длину size, над- и под- диагонали – соответственно size-1. Во избежание смещения индексов используется маленькая хитрость: по ссылке (а фортрановская DGTSV интерпретирует сcылку как указатель) передается элемент с индексом 1 из массива длиной size, а не элемент с индексом 0 из массива длиной size – 1. И все прекрасно работает…
Теперь пытаюсь проделать то же в C#. Используя скомпилированные в x64 .dll библиотеки – это x64 версия liblapack.dll, которая явно требует x64 версии libblas.dll, а неявно еще и libgfortran_64-3.dll, libgcc_s_seh_64-1.dll, libquadmath-0.dll, решил разобраться с простым консольным x64 приложением на C#.
using System;
using System.Runtime.InteropServices;
namespace Linalg
{
class Program
{
[DllImport("liblapack.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern void dgtsv_(in int size1, in int nrhs, in double c0, in double c1, in double c2, in double v, in int size2, out int info);
static void Main(string[] args)
{
int size = 3;
int nrhs = 1;
double[] c0 = new double[] { 0.0, 1.0, 0.0 };
double[] c1 = new double[] { 1.0, -2.0, 1.0 };
double[] c2 = new double[] { 0.0, 1.0, 0.0 };
double[] v = new double[] { 1.0, 2.0, 3.0 };
int info = 0;
dgtsv_(in size, in nrhs, in c0[1], in c1[0], in c2[0], in v[0], in size, out info);
Console.WriteLine("info = {0}", info);
}
}
}
При запуске – System.BadImageFormatException: "Была сделана попытка загрузить программу, имеющую неверный формат. (0x8007000B)"
Люди, добрые – что не так? )))
Кажется, я таки допинал C# и OpenBLAS. Малой кровью получилось с Visual Studio 2010 (32-битная версия). Пример реально работающей программы с 32-битными OpenBLAS и с референсными BLAS/LAPACK (пример честно стащил отсюда и совсем немного подправил название библиотеки):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
namespace VAK_lapack_test
{
class MainClass
{
//[DllImport("liblapack.dll", EntryPoint = "dgesv_")]
[DllImport("libopenblas.dll", EntryPoint = "dgesv_")]
static extern void lapack_dgesv(ref int n, ref int nrhs, double[] a, ref int lda, int[] ipvt, double[] b, ref int ldb, ref int infos);
public static void Main(string[] args)
{
// 5X - 2Y = 7
// -X + Y = 1
double[] a = { 5, -1, -2, 1 };
double[] b = { 7, 1 };
int n = 2;
int nrhs = 1;
int lda = 2;
int ldb = 2;
int infos0 = 0;
var a0 = a.ToArray();
var b0 = b.ToArray();
int[] ipvt0 = new int[n];
lapack_dgesv(ref n, ref nrhs, a0, ref lda, ipvt0, b0, ref ldb, ref infos0);
Console.WriteLine("Equations");
Console.WriteLine("5X - 2Y = 7");
Console.WriteLine("-X + Y = 1");
Console.WriteLine();
Console.WriteLine("Solutions netlib lapack");
//Console.WriteLine($"X = {b0[0]} and Y = {b0[1]}");
Console.WriteLine("X = {0} and Y = {1}", b0[0], b0[1]);
Console.WriteLine();
}
}
}
И самое главное. Это работает только с моими специально обученными библиотеками. Специальная обученность их заключается в том, что они собраны с помощью MinGW GCC 4.9 (Об особенностях этой версии можно немного почитать в официальном документе How to use OpenBLAS in Microsoft Visual Studio; но там не говорится, что это единственная возможность. Как я писал, это просто самый простой вариант в моих условиях).
Для 32-битных версий, теоретически, можно использовать динамические библиотеки от MinGW с верисями, новее чем gcc 4.7, но у меня почему-то это не вышло с более новыми версиями (имеются ввиду, версии MinGW GCC, которым я собирал библиотеку OpenBLAS). А вот для 64-битной версии, как я понял, других вариантов нет - библиотеки нужно собирать в самом Visual Studio.
Теперь, когда есть работающий пример, можно начинать искать набор библиотек, совместимых с VS (можно попробовать вот отсюда - я не пробовал, это Яндекс подсказывает; но это оригинальные библиотеки с Netlib.org, а не OpenBLAS).
Correct answer by Vladimir on August 30, 2021
Выяснился еще один нюанс - в Компонентом Паскале (среда BlackBoox Component Builder) я использовал следующие конструкции.
При описании:
PROCEDURE DGTSV* ["dgtsv_"] (VAR n : LAPACK_Integer; VAR nrhs : LAPACK_Integer;
VAR dl : Double; VAR d : Double; VAR du : Double; VAR b : Double;
VAR ldb : LAPACK_Integer; VAR info : LAPACK_Integer);
При использовании:
LAPACK.DGTSV (size, nrhs, c0[1], c1[0], c2[0], v[0], size, info);
Обращаю внимание на VAR dl, d, du, b : Double; (VAR - указание на использование передачи по ссылке, а не по значению, аналог в c# - ref) - это не dl, d, du, b : POINTER TO ARRAY OF Double;. И при вызове стоят не указатели на массивы c0, c1, c2, v, а указатели на элементы массивов c0[1], c1[0], c2[0], v[0]. Причём, c0[1] - это указатель на элемент массива с индексом 1 (индексация с 0). К моему удивлению, если в исходнике C# Linalg подправить описание с
static extern void lapack_dgtsv(ref int n, ref int nrhs, double[] dl, double[] d, double[] du, double[] v, ref int ldb, ref int info);
на
static extern void lapack_dgtsv(ref int n, ref int nrhs, ref double dl, ref double d, ref double du, ref double v, ref int ldb, ref int info);
то и вызов можно сделать не
lapack_dgtsv (ref size, ref nrhs, c0, c1, c2, v, ref size, ref info);
а
lapack_dgtsv(ref size, ref nrhs, ref c0[1], ref c1[0], ref c2[0], ref v[0], ref size, ref info);
т.е. тот же трюк, что работал в Компонентном Паскале можно провернуть и тут (c0[1])!!!
Итак, решаем одномерную стационарную задачу теплопроводности d2u/dx2 = 0 на трёх точках - да, это смешно, но это самый простой тест. Вторая производная аппроксимируется так: u[2] - 2*u[1] + u[0] = 0. Левое граничное условие - u[0] = 0, правое - u[1] = 1. Матрица коэффициентов и прочее:
0 [1 0 0] [u0] [0]
[1 -2 1] * [u1] = [0]
[0 0 1] 0 [u2] [1]
Стационарное решение - прямая x = i/(N-1), где i = 0..2, N = 3, т.е. u = (0, 0.5, 1). Нулевые элементы матрицы за пределами []-скобок - как бы продолжения над- и под-диагоналей.
Основная диагональ - (1, -2, 1). Нижняя диагональ - (1, 0), а с учетом дополнительного элемента (0, 1, 0). Аналогично - верхняя: (0, 1, 0)
Дополнительные элементы как-бы не используются, но есть. Они позволяют избежать смещения индексов: с0[i-1] вместо желаемого с0[i].
с0[i-1] := a[i,i-1];
с1[i] := a[i,i];
с2[i] := a[i,i+1];
Исходный код:
using System;
using System.Runtime.InteropServices;
namespace Linalg
{
class Program
{
[DllImport("openblas.dll", EntryPoint = "dgtsv_")]
static extern void lapack_dgtsv(ref int n, ref int nrhs, ref double dl, ref double d, ref double du, ref double v, ref int ldb, ref int info);
static void Main(string[] args)
{
int size = 3;
int nrhs = 1;
double[] c0 = new double[] { 0.0, 1.0, 0.0 };
double[] c1 = new double[] { 1.0, -2.0, 1.0 };
double[] c2 = new double[] { 0.0, 1.0, 0.0 };
double[] v = new double[] { 0.0, 0.0, 1.0 };
int info = 0;
Console.WriteLine("v = ({0}, {1}, {2})", v[0], v[1], v[2]);
lapack_dgtsv(ref size, ref nrhs, ref c0[1], ref c1[0], ref c2[0], ref v[0], ref size, ref info);
Console.WriteLine("info = {0}", info);
Console.WriteLine("v = ({0}, {1}, {2})", v[0], v[1], v[2]);
}
}
}
И да - это работает!
Answered by serg.tortilliani on August 30, 2021
Огроменное спасибище Владимиру https://ru.stackoverflow.com/users/248924/vladimir
Решение нашлось.
Сначала в VS2019 добавляем поддержку C++. Он в процессе потребуется и без файла vcvars64.bat не обойтись.
Делаем всё по инструкции: https://github.com/xianyi/OpenBLAS/wiki/How-to-use-OpenBLAS-in-Microsoft-Visual-Studio
Качаем OpenBLAS c https://sourceforge.net/projects/openblas/files/v0.3.10/ (https://sourceforge.net/projects/openblas/files/v0.3.10/OpenBLAS%200.3.10%20version.tar.gz/download)
Качаем и устанавливаем 64-битную миниконду со страницы https://docs.conda.io/en/latest/miniconda.html (https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe)
Все операции я проводил в корне C: - у меня это SSD. Создал папку C:opt - она потребуется в дальнейшем. Распаковал архив с OpenBLAS и переименовал его в C:OpenBLAS. Запускаем консоль анаконды "Anaconda Prompt (Miniconda3)" - я её запустил с правами администратора, на всякий - во избежание возможных проблем с правами на запись. Переходим в консоли в папку с OpenBLAS'ом:
cd
cd openblas
В консоли отображается
(base) C:OpenBLAS>
Все команды вводим в консоли. Поехали!
conda update -n base conda
conda config --add channels conda-forge
conda install -y cmake flang clangdev perl libflang
conda install -y -c isuruf kitware-ninja
Затем
"C:Program Files (x86)Microsoft Visual Studio2019CommunityVCAuxiliaryBuildvcvars64.bat"
Создаем в C:OpenBLAS .bat-файл с таким содержимым (опция -DBUILD_SHARED_LIBS=ON обязательна). Я назвал его zx.bat:
set "LIB=%CONDA_PREFIX%Librarylib;%LIB%"
set "CPATH=%CONDA_PREFIX%Libraryinclude;%CPATH%"
mkdir build
cd build
cmake .. -G "Ninja" -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER=clang-cl -DCMAKE_Fortran_COMPILER=flang -DBUILD_WITHOUT_LAPACK=no -DNOFORTRAN=0 -DDYNAMIC_ARCH=ON -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release
В консоли анаконды его запускаем:
zx
Переходим к самому главному и самому долгому:
cmake --build . --config Release
После окончания компиляции завершаем всё командой:
cmake --install . --prefix c:opt -v
Забегая вперед - нужно ввести еще одну, самую последнюю команду :)
conda install libflang
Заходим в C:optbin и видим свежеиспечённую openblas.dll. В 64-битном Dependency walker'е (он проживает по адресу https://www.dependencywalker.com/) можно увидеть зависимости этой dll. Нужны оказываются ещё и некие flang.dll и flangrti.dll, а сама flang.dll потребует libomp.dll. После команды conda install libflang они как раз установятся и найти их можно в папке "C:Documents and SettingsAll UsersMiniconda3Librarybin".
Теперь запускаем Visual Studio 2019 (ну у кого что), создаем консольное приложение. В диспетчере конфигурации сразу выставляем платформу x64.
Исходный код:
using System;
using System.Runtime.InteropServices;
namespace Linalg
{
class Program
{
[DllImport("openblas.dll", EntryPoint = "dgtsv_")]
static extern void lapack_dgtsv(ref int n, ref int nrhs, double[] c0, double[] c1, double[] c2, double[] v, ref int ldb, ref int info);
static void Main(string[] args)
{
int size = 3;
int nrhs = 1;
double[] c0 = new double[] { 1.0, 0.0, 0.0 };
double[] c1 = new double[] { 1.0, -2.0, 1.0 };
double[] c2 = new double[] { 0.0, 0.0, 1.0 };
double[] v = new double[] { 1.0, 2.0, 3.0 };
int info = 0;
lapack_dgtsv (ref size, ref nrhs, c0, c1, c2, v, ref size, ref info);
Console.WriteLine("info = {0}", info);
}
}
}
Всем большое спасибо за участие.
Answered by serg.tortilliani on August 30, 2021
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP