Co to są funkcje użytkownika?

Funkcje użytkownika (User Defined Function, UDF) są jednym z powodów dużej popularności oprogramowania ANSYS Fluent na uczelniach, ale znajdują szerokie zastosowanie również w przemyśle. Są niczym innym jak możliwością dodania dodatkowej funkcjonalności niedostępnej standardowo w programie ANSYS Fluent. Poziom skomplikowania UDF-ów może być różny, od prostych warunków brzegowych po implementacje całych modeli fizycznych. Szereg oficjalnych dodatkowych modułów do ANSYS Fluent jest udostępnianych między innymi właśnie w postaci UDF-ów.

Co potrzeba aby zacząć pisanie własnych funkcji użytkownika?

Oprócz samego oprogramowania ANSYS Fluent, niewiele:

  1. Zainstalowany kompilator C (dla różnych wersji ANSYS Fluent zalecane są różne wersje kompilatora, po aktualne zalecenia skontaktuj się z nami)
  2. Podstawowa wiedza o programowaniu w języku C
  3. Edytor tekstu najlepiej wspomagający programowanie. Ja używam darmowego Programmer’s Notepad, popularny jest też Notepad++

Z czego składa się każdy plik z kodem UDF?

W pierwszej linijce umieszczamy zawsze dyrektywę include wskazującą na plik nagłówkowy udf.h zawierający deklaracje wszystkich makr i typów dostarczanych przez ANSYS Fluent:

#include “udf.h”

W dalszej części kodu umieszczamy napisane przez nas funkcje oraz implementacje przynajmniej jednego makra DEFINE_. Co to są makra DEFINE_? Mówiąc obrazowo, są to wytrychy które przygotował dla nas ANSYS Fluent, za pomocą których możemy dostać się do jego wnętrza. Makra te dzielą się na ogólnego przeznaczenia oraz bardziej specyficzne przystosowane do konkretnych zadań. Jedną z pierwszą rzeczy, jakie powinieno się zrobić rozpoczynając pracę z UDF-ami to zapoznanie się z najbardziej popularnymi makrami DEFINE_.

Napiszemy pierwszy UDF?

Jednym z najprostszych UDF-ów jest napisanie własnego warunku brzegowego, np. zmiennej w czasie temperatury. Załóżmy że temperatura ma się zmieniać:

  • Od 0 s do 1 s symulacji ma rosnąć liniowo od 300 do 350 K.
  • Od 1 s symulacji ma być stała i równa 350 K.

Do definiowania warunków brzegowych służy najczęściej makro DEFINE_PROFILE. Skąd to wiemy? Z dokumentacji ANSYS Fluent, w której zawarty jest obszerny rozdział poświęcony UDF-om. Będziemy z niego często korzystali. Otwórz teraz dokumentację ANSYS Fluent i znajdź opis makra DEFINE_PROFILE

W opisie każdego makra DEFINE_ znajdziemy ogólne informacje o jego zastosowaniu, następnie opis parametrów przekazywanych przez makro, a na końcu przykładowy kod z jego wykorzystaniem.

Popatrzmy na parametry jakie przekazuje makro DEFINE_PROFILE:

DEFINE_PROFILE (name, t, i)

  • name – jest to po prostu nazwa po której rozpoznamy makro we Fluencie, w każdym makrze DEFINE_ pierwszy parametr to właśnie nazwa,
  • t – jest to wskaźnik na wątek zawierający ścianki komórek w wybranym we Fluencie warunku brzegowym, pisząc UDF nie wiemy jeszcze jaki zostanie wybrany,
  • i – jest to numer identyfikujący do jakiej zmiennej na warunku brzegowym będziemy wprowadzać wartość (czy np. prędkość czy temperatura), pisząc UDF nie wiemy jeszcze jaki zostanie wybrany.

Zrobiło się trochę skomplikowanie, jak właściwie ma wyglądać nasz UDF?

Przejdźmy do konkretów, na przykładzie wszystko stanie się jasne. Poniżej przedstawiam zawartość pliku tekstowego, który edytujemy (powinien mieć rozszerzenie .c):

#include “udf.h”

#include “udf.h”

#define TempMax 350.0                                   //to jest komentarz

#define TempStart 300.0                                  //definicja stałych

#define TimePeriod 1.0

 

DEFINE_PROFILE(temperature_profile, ft, var)

{

face_t f;                                                                 //definicja zmiennej reprezentującej ściankę komórki, w rzeczywistości

                                                                                //jest to zwykła zmienna typu całkowitego

real temp;                                                              //deklaracja zmiennej typu rzeczywistego

real flow_time;                                                      //deklaracja zmiennej typu rzeczywistego

 

flow_time = CURRENT_TIME;                            //przypisanie aktualnego czasu symulacji do zmiennej flow_time

                                                                               //CURRENT_TIME jest to makro dostarczane przez ANSYS Fluent

 

if (flow_time<TimeV)

    temp=TempStart+flow_time/TimePeriod*(TempMax-TempStart);      //obliczam temperaturę w funkcji czasu

 

else

    temp=TempMax;                                            //po upływie 1 sekundy

                                                                              //temperatura się już nie zmienia

begin_f_loop(f,ft)                                               //wykorzystuję pętlę dostarczoną przez ANSYS Fluent aby odwiedzić

                                                                             //wszystkie ścianki komórek (f) należących do wątku ft

    {

    F_PROFILE(f,ft,var) = temp;                          //przypisuję wartości temperatury do konkretnych ścianek                                                                                         

                                                                             //komórek f wątku ft

    }

end_f_loop(f,ft)

}

W komentarzach w kodzie umieściłem krótkie wyjaśnienia poszczególnych linijek. Teraz dodam jeszcze kilka uwag.

ANSYS Fluent dostarcza nam przygotowane typy reprezentujące poszczególne elementy naszego modelu numerycznego:

  • Node – struktura przechowująca informacje o węzłach siatki, dokładnie o ich współrzędnych
  • face_t – zmienna reprezentująca ściankę komórki, w rzeczywistości jest to zwykła zmienna typu całkowitego
  • cell_t – – zmienna reprezentująca komórkę, w rzeczywistości jest to zwykła zmienna typu całkowitego
  • Thread – struktura przechowująca komórki lub ścianki komórek, reprezentuje warunki brzegowe lub objętości przepływowe
  • Domain – struktura zawierająca wszystkie warunki brzegowe i objętości przepływowe

Patrząc na samą konwencję nazewnictwa można rozróżnić które zmienne to typy złożone (nazwa typu rozpoczyna się dużą literą), a które to typy proste (mała litera). Jako że zmienne reprezentujące ścianki i komórki to zwykłe numery, do pełnego zidentyfikowania danej ścianki lub komórki potrzebujemy zarówno jej numer (zmienna typu face_t lub cel_t), jak i wskaźnik na strukturę w której się znajduje (zmienna typu Thread).

W naszym kodzie wykorzystujemy właśnie wskaźnik na warunek brzegowy, który jest przekazany jako parametr funkcji (ft) oraz definiujemy nową zmienną f reprezentującą ścianki komórek. Oprócz tego zdefiniowaliśmy dwie zmienne real – tutaj wyjaśnienie: zmienna typu real służy do przechowywania liczb rzeczywistych, w zależności od ustawień solwera przełącza się pomiędzy zapisem w pojedynczej i podwójnej precyzji. z tego względu zwykle jest zalecane stosowanie typu real zamiast float czy double.

Następnie makro CURRENT_TIME zwraca nam aktualny fizyczny czas symulacji, który potrzebny będzie nam do policzenia wartości temperatury. Po obliczeniu wartości temperatury za pomocą pętli begin_f_loop(f,ft) odwiedzam wszystkie ścianki (f) warunku brzegowego ft i wprowadzam tam wyliczoną wartość. Na tym etapie nie jest określone czy wprowadzona wartość ma oznaczać temperaturę, prędkość czy inną zmienną. Definiowane jest w momencie podpięcie naszej funkcji do odpowiedniego warunku brzegowego w ANSYS Fluent.

Co dalej?

Teraz pozostaje nam wczytać w jakiś sposób naszą funkcję do ANSYS Fluent. Mamy dwie możliwości: możemy wczytać przygotowany UDF jako kod interpretowany lub skompilować go do biblioteki, a następnie ją wczytać. Naszą funkcję podpinamy do odpowiedniego warunku brzegowego i cieszymy się naszym pierwszym działającym UDF-em.

Dr inż. Adam Piechna
Symkom

Zapraszamy do zapoznania się z ofertą szkoleń.