Как стать автором
Обновить

Фракталы, Fortran и OpenMP

Время на прочтение 6 мин
Количество просмотров 14K
Когда-то давно я решил «потрогать» Fortran. Единственную задачу которую я придумал — генерация фракталов (заодно и OpenMP в Fortran'е можно было бы попробовать). В процессе написания я часто сталкивался с проблемами, решение которых приходилось додумывать самому (например в интернете не так много примеров использования чисел двойной точности или бинарной записи в файл). Но рано или поздно все проблемы решились, и я хочу написать этот текст, который возможно кому-нибудь поможет.

Писать я буду на диалекте Fortran 90, но с GNU расширениями (те же числа двойной точности).


Немного теории о фракталах


Фрактал (fractus – дробленый, сломанный, разбитый) — в узком смысле, сложная геометрическая фигура, обладающая свойством самоподобия, то есть составленная из нескольких частей, каждая из которых подобна всей фигуре целиком.

Существует большая группа фракталов — алгебраические фракталы. Это название вытекает из принципа построения таких фракталов. Для их построения используют рекурсивные функции. Например алгебраическими фракталами являются: Множество Жюлиа, Множество Мандельброта, Бассейн(фрактал) Ньютона.

Построение алгебраических фракталов


Один из методов построения представляет собой итерационный расчет image, где image, а image некая функция. Расчет данной функции продолжается до выполнения определенного условия. Когда это условие выполнится на экран выводится точка.

Поведение функции для разных точек комплексной плоскости может иметь разное поведение:
  • Стремится к бесконечности
  • Стремится к 0
  • Принимает несколько фиксированных значений и не выходит за их пределы
  • Хаотичное поведение. Отсутствие каких либо тенденций

Один из простейших (как для понимания, так и для реализации) алгоритмов построения алгебраических фракталов известен как «escape time algorithm». Если кратко, то производится итеративный расчет числа для каждой точки комплексной плоскости.

Было доказано, что если image окажется больше bailout, то последовательность image. Сравнение image с bailout позволяет находить точки лежащие вне множества. Для точек, лежащих вне множества, последовательность image не будет иметь тенденции к бесконечности, поэтому никогда не достигнет bailout.

Я рассмотрю два простых фрактала – Множество Жюлиа и Множество Мандельброта. Расчитываются они по одной и той-же функции, а отличаются лишь константой, где у Жюлиа это постоянная константа, а у Мандельброба константа зависит от точки комплексной плоскости.

Построение Множества Жюлиа


Начало и конец программы простой и тривиальный:
program frac
end

Дальше нам нужно ввести несколько констант:
	INTEGER, PARAMETER :: iterations = 2048 !Количество итераций. Чем больше, тем глубже будет идти просчет

	REAL(8), PARAMETER :: mag_ratio = 1.0_8 !Приближение

	REAL(8), PARAMETER :: x0 = 0.0_8 !Смещение комплексной плоскости
	REAL(8), PARAMETER :: y0 = 0.0_8 !

	INTEGER, PARAMETER :: resox = 1024 !Разрешение получившегося изображения
	INTEGER, PARAMETER :: resoy = resox

	REAL(8), PARAMETER :: xshift = (-1.5_8 / mag_ratio) + x0 !Смещение центра комплексной оси в
	REAL(8), PARAMETER :: yshift = (-1.5_8 / mag_ratio) + y0 !центр изображения

	REAL(8), PARAMETER :: CXmin = -1.5_8 / mag_ratio !Следующие константы растягивают
	REAL(8), PARAMETER :: CXmax = 1.5_8 / mag_ratio  !комплексную плоскость до размеров resox x resoy
	REAL(8), PARAMETER :: CXshag = (DABS(CXmin) + DABS(CXmax)) / resox !Связываем значение одного пикселя и точки на комплексной плоскости
	REAL(8), PARAMETER :: CYmin = -1.5_8 / mag_ratio
	REAL(8), PARAMETER :: CYmax = 1.5_8 / mag_ratio
	REAL(8), PARAMETER :: CYshag = (DABS(CYmin) + DABS(CYmax)) / resoy

	COMPLEX(8), PARAMETER :: c = DCMPLX(0.285_8, 0.01_8) !Наша основная константа

И вот тут нужно кое-что объяснить. REAL и COMPLEX это число с плавающей точкой одинарной точности и комплексное число, состоящее из двух REAL. REAL(8) это число двойной точности, а COMPLEX(8), соотвественно, комплексное число, состоящее из двух REAL(8). Так-же к стандартным функциям добавляется литера D (как в случае с ABS), что указывает на использование чисел двойной точности.

Дальше нам нужно ввести несколько переменных:
	CHARACTER, DIMENSION(:), ALLOCATABLE :: matrix !Массив точек

	COMPLEX(8) :: z
	INTEGER :: x, y, iter
	REAL(8) :: iter2 !Понадобится нам для сглаживания

	ALLOCATE(matrix(0 : resox * resoy * 3))

Использование ALLOCATABLE обязательно! Т.к. в ином случае:
	CHARACTER, DIMENSION(0:resox * resoy * 3) :: matrix !Массив точек

Память у нас выделится на стеке, а это не приемлемо при использовании в нескольких потоках. Поэтому мы выделяем память на куче.

Так-же я не использую двумерные массивы, т.к. при выделении на куче массив будет занимать больше места, да и записать его в файл будет сложнее.

Дальше нам нужно указать количество потоков, порождаемых OpenMP:
	call omp_set_num_threads(16)

А тут начинаются непосредственно вычисления. В Fortran'е директивы для OpenMP указываются через коментарии (в C/C++ для это есть специльный макрос #pragma).
	!$OMP PARALLEL DO PRIVATE(iter, iter2, z)
	do x = 0, resox-1
		do y = 0, resoy-1
			iter = 0 !Обнуляем количество итераций
			z = DCMPLX(x * CXshag + xshift, y * CYshag + yshift) !Задаем Z начальное значение
			do while ((iter .le. iterations) .and. (CDABS(z) .le. 4.0_8)) !Обычно за bailout берут 2, но я взял 4, т.к. результат лучше сглаживается
				z = z**2 + c
				iter = iter + 1
			end do

			iter2 = DFLOAT(iter) - DLOG(DLOG(CDABS(z))) / DLOG(2.0_8) !Формула для сглаживания iter-log2(ln(|Z|))
			matrix((x + y * resox) * 3 + 0) = CHAR(NINT(DMOD(iter2 * 7.0_8, 256.0_8))) !Один из способов расскрашивания
			matrix((x + y * resox) * 3 + 1) = CHAR(NINT(DMOD(iter2 * 14.0_8, 256.0_8)))
			matrix((x + y * resox) * 3 + 2) = CHAR(NINT(DMOD(iter2 * 2.0_8, 256.0_8)))
		end do
	end do
	!$OMP END PARALLEL DO

Самая трудоемкая часть закончена. Дальше нам остается вывести информацию в удобном для восприятия виде — изображение. Я буду использовать формат PNM, т.к. это самый простой формат изображений.

PNM состоит из нескольких секций:
P6
#Комментарий
1024 1024
255

Первая строчка это указание формата записи информации о пикселях:
  • P1/P4 — черно-белое изображение
  • P2/P5 — серое изображение
  • P3/P6 — цветное изображение

Первая P это вариант, где цвет пикселя записывается ASCII символом, а вторая P дает возможность записывать цвет пикселя в бинарном виде (что значительно экономит место на диске).

Следующей строкой идет комментарий, дальше разрешение изображения и количество цветов на пиксель. После количества цветов идет непосредственно информация о пикселях.

Начинаем записывать изображение в файл:
	open(unit=8, status = 'replace', file = trim("test.pnm"))
	write(8, '(a)') "P6" !(a) это текстовая строка
	write(8, '(a)') ""
	write(8, '(I0, a, I0)') resox, ' ', resoy
	write(8, '(I0)') 255
	write(8, *) matrix
	close(8)

	DEALLOCATE(matrix)

DEALLOCATE мы выполняем для приличия, ибо при выходе из программы, ОС все равно вернет занятую память системе.

Для сборки программы можно использовать компилятор от GNU — gfortran:
gfortran -std=gnu frac.f90 -o frac.run -fopenmp

Должно получится следующее изображение:
1024x1024


Как сгенерировать Множество Мандельброта


Это сделать несложно, достаточно заменить c и изменить несколько констант. В данном случаем убираем константу c и добавляем переменную c:
	COMPLEX(8) :: z, c

			z = DCMPLX(x * CXshag + xshift, y * CYshag + yshift) !Задаем Z начальное значение
			c = z

А так-же меняем старые константы:
	INTEGER, PARAMETER :: MAIN_RES = 1024
	INTEGER, PARAMETER :: resox = (MAIN_RES / 3) * 3
	INTEGER, PARAMETER :: resoy = (resox * 2) / 3

	REAL(8), PARAMETER :: xshift = (-2.0_8 / mag_ratio) + x0
	REAL(8), PARAMETER :: yshift = (-1.0_8 / mag_ratio) + y0

	REAL(8), PARAMETER :: CXmin = -2.0_8 / mag_ratio
	REAL(8), PARAMETER :: CXmax = 1.0_8 / mag_ratio
	REAL(8), PARAMETER :: CXshag = (DABS(CXmin) + DABS(CXmax)) / resox
	REAL(8), PARAMETER :: CYmin = -1.0_8 / mag_ratio
	REAL(8), PARAMETER :: CYmax = 1.0_8 / mag_ratio
	REAL(8), PARAMETER :: CYshag = (DABS(CYmin) + DABS(CYmax)) / resoy

Должно получится следующее изображение:
1023x682


upd
После того как я поработал на языке Ada я нашел одну ошибку — там где я использовал Mod. Это функция играла у меня роль костыля, т.к. у нас на один канал цвета принимает значения от 0 до 255, то получалось что Mod'ом я делал так, чтоб значение не было выше максимального. Как вы можете видеть на изображениях это приводит к резким переходам в цветах.
Исправлю я это с помощью это формулы: image. Получается вариация линейной интерполяции в линейном пространстве.
Чтоб было проще в использовании, мы напишем следующую функцию:
function myround(a) result(ret)
	REAL(8), intent(in) :: a
	INTEGER :: ret !Для удобства мы сразу взяли b как 255, сразу приводим к INTEGER и убираем знак.
	ret = ABS(INT(a -  255.0_8 * NINT(a / 255.0_8))) !NINT() это функция аналогичная round() в C, т.е. округляем к ближайшему целому
end function myround

program frac
    INTEGER :: myround

И заменяем присваивание цвета точкам:
	matrix((x + y * resox) * 3 + 0) = CHAR(myround(iter2 * 7.0_8))
	matrix((x + y * resox) * 3 + 1) = CHAR(myround(iter2 * 14.0_8))
	matrix((x + y * resox) * 3 + 2) = CHAR(myround(iter2 * 2.0_8))

Получается следующее изображение:
1024x1024


upd2
Т.к. в Fortran'е как и в Ada автоматически выбирается EOL, то под не Unix системами PNM получается неправильным. Дабы этого избежать, придется использовать следующий велосипед:
	write(8, '(a, a, $)') "P6", char(10)
	write(8, '(a,$)') char(10)
	write(8, '(I0, a, I0, a, $)') resox, ' ', resoy, char(10)
	write(8, '(I0, a, $)') 255, char(10)
	write(8, *) matrix


Используемая литература


en.wikipedia.org/wiki/Fractal
en.wikipedia.org/wiki/Julia_set
en.wikipedia.org/wiki/Mandelbrot_set
en.wikipedia.org/wiki/OpenMP
openmp.org/wp/openmp-specifications
gcc.gnu.org/onlinedocs/gfortran
Теги:
Хабы:
+14
Комментарии 65
Комментарии Комментарии 65

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн
PG Bootcamp 2024
Дата 16 апреля
Время 09:30 – 21:00
Место
Минск Онлайн
EvaConf 2024
Дата 16 апреля
Время 11:00 – 16:00
Место
Москва Онлайн