xref: /titanic_41/usr/src/lib/libshell/common/scripts/mandelbrotset1.sh (revision a4dd1f3517267c5e5aa5b2bb53cb388002bc688f)
1#!/usr/bin/ksh93
2
3#
4# CDDL HEADER START
5#
6# The contents of this file are subject to the terms of the
7# Common Development and Distribution License (the "License").
8# You may not use this file except in compliance with the License.
9#
10# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
11# or http://www.opensolaris.org/os/licensing.
12# See the License for the specific language governing permissions
13# and limitations under the License.
14#
15# When distributing Covered Code, include this CDDL HEADER in each
16# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
17# If applicable, add the following below this CDDL HEADER, with the
18# fields enclosed by brackets "[]" replaced with your own identifying
19# information: Portions Copyright [yyyy] [name of copyright owner]
20#
21# CDDL HEADER END
22#
23
24#
25# Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
26#
27
28#
29# mandelbrotset1 - a simple mandelbrot set generation and
30# parallel execution demo
31#
32
33# Solaris needs /usr/xpg6/bin:/usr/xpg4/bin because the tools in /usr/bin are not POSIX-conformant
34export PATH=/usr/xpg6/bin:/usr/xpg4/bin:/bin:/usr/bin
35
36# Make sure all math stuff runs in the "C" locale to avoid problems
37# with alternative # radix point representations (e.g. ',' instead of
38# '.' in de_DE.*-locales). This needs to be set _before_ any
39# floating-point constants are defined in this script).
40if [[ "${LC_ALL}" != "" ]] ; then
41    export \
42        LC_MONETARY="${LC_ALL}" \
43        LC_MESSAGES="${LC_ALL}" \
44        LC_COLLATE="${LC_ALL}" \
45        LC_CTYPE="${LC_ALL}"
46        unset LC_ALL
47fi
48export LC_NUMERIC=C
49
50function printmsg
51{
52	print -u2 "$*"
53}
54
55function fatal_error
56{
57	print -u2 "${progname}: $*"
58	exit 1
59}
60
61# Get terminal size and put values into a compound variable with the integer
62# members "columns" and "lines"
63function get_term_size
64{
65	nameref rect=$1
66
67	rect.columns=${ tput cols ; } || return 1
68	rect.lines=${ tput lines ; }  || return 1
69
70	return 0
71}
72
73function mandelbrot
74{
75	nameref result=$1
76	float   x=$2
77	float   y=$3
78	float   xx
79	float   yy
80	float   x1=$4
81	float   y1=$5
82	integer iteration=$6
83	integer max_iteration=$7
84	float   mag
85
86	for (( mag=0 ; mag < max_mag && iteration < max_iteration ; iteration++ )) ; do
87		((
88			xx=x*x ,
89			yy=y*y ,
90			mag=xx+yy ,
91			y=x*y*2+y1 ,
92			x=xx-yy+x1
93		))
94	done
95
96	(( result=iteration ))
97
98	return 0
99}
100
101# build mandelbrot image serially
102function loop_serial
103{
104	integer value
105	typeset line=""
106
107	for (( y=y_min ; y < y_max ; y+=stepwidth )) ; do
108		for (( x=x_min ; x < x_max ; x+=stepwidth )) ; do
109			mandelbrot value ${x} ${y} ${x} ${y} 1 ${symbollistlen}
110			line+="${symbollist:value:1}"
111		done
112
113		line+=$'\n'
114	done
115
116	print -r -- "${line}"
117
118	return 0
119}
120
121# build mandelbrot image using parallel worker jobs
122function loop_parallel
123{
124	integer numjobs=0
125	# the following calculation suffers from rounding errors
126	integer lines_per_job=$(( ((m_height+(numcpus-1)) / numcpus) ))
127	typeset tmpjobdir
128
129	printmsg $"# lines_per_job=${lines_per_job}"
130	printmsg $"# numcpus=${numcpus}"
131
132	# "renice" worker jobs
133	set -o bgnice
134
135	tmpjobdir="$(mktemp --default=/tmp --directory "mandelbrotset1${PPID}_$$_XXXXXX")" || fatal_error $"Could not create temporary directory."
136	trap "rm -r ${tmpjobdir}" EXIT # cleanup
137
138	# try to generate a job identifer prefix which is unique across multiple hosts
139	jobident="job_host_$(uname -n)pid_$$_ppid${PPID}"
140
141	printmsg $"## prepare..."
142	for (( y=y_min ; y < y_max ; y+=(stepwidth*lines_per_job) )) ; do
143		rm -f "${tmpjobdir}/${jobident}_child_$y.joboutput"
144
145		(( numjobs++ ))
146	done
147
148	printmsg $"## running ${numjobs} children..."
149	for (( y=y_min ; y < y_max ; y+=(stepwidth*lines_per_job) )) ; do
150		(
151			integer value
152			typeset line=""
153			# save file name since we're going to modify "y"
154			typeset filename="${tmpjobdir}/${jobident}_child_$y.joboutput"
155
156			for (( ; y < y_max && lines_per_job-- > 0 ; y+=stepwidth )) ; do
157				for (( x=x_min ; x < x_max ; x+=stepwidth )) ; do
158					mandelbrot value ${x} ${y} ${x} ${y} 1 ${symbollistlen}
159					line+="${symbollist:value:1}"
160				done
161
162				line+=$'\n'
163			done
164			print -r -- "${line}" >"${filename}"
165
166			exit 0
167		) &
168	done
169
170	printmsg $"## waiting for ${numjobs} children..."
171	wait
172
173	printmsg $"## output:"
174	for (( y=y_min ; y < y_max ; y+=(stepwidth*lines_per_job) )) ; do
175		print -r -- "$( < "${tmpjobdir}/${jobident}_child_$y.joboutput")"
176		# EXIT trap will cleanup temporary files
177	done
178
179	return 0
180}
181
182function usage
183{
184	OPTIND=0
185	getopts -a "${progname}" "${mandelbrotset1_usage}" OPT '-?'
186	exit 2
187}
188
189# main
190builtin basename
191builtin cat
192builtin rm
193builtin uname # loop_parallel needs the ksh93 builtin version to generate unique job file names
194builtin mktemp
195
196set -o noglob
197set -o nounset
198
199typeset progname="${ basename "${0}" ; }"
200
201float x_max
202float x_min
203float y_max
204float y_min
205float m_width
206float m_height
207float max_mag
208float stepwidth
209integer numcpus
210
211# terminal size rect
212compound termsize=(
213	integer columns=-1
214	integer lines=-1
215)
216
217get_term_size termsize || fatal_error $"Could not get terminal size."
218
219typeset symbollist='    .:0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%#'
220typeset symbollistlen=$(( ${#symbollist} - 1))
221typeset mode="parallel"
222
223(( max_mag=400 ))
224(( stepwidth=0.1 ))
225
226# calculate number of worker CPUs and use 3 as fallback
227(( numcpus=$(getconf NPROCESSORS_ONLN || print "3") ))
228(( numcpus=numcpus*4 ))
229
230(( m_width=termsize.columns-1 , m_height=termsize.lines-2 ))
231
232typeset -r mandelbrotset1_usage=$'+
233[-?\n@(#)\$Id: mandelbrotset1 (Roland Mainz) 2010-03-31 \$\n]
234[-author?Roland Mainz <roland.mainz@nrubsig.org>]
235[+NAME?mandelbrotset1 - generate mandelbrot set fractals with ksh93]
236[+DESCRIPTION?\bmandelbrotset1\b mandelbrot set fractal generator
237	which runs either in serial or parallel mode (using multiple worker jobs).]
238[w:width?Width of fractal.]:[width]
239[h:height?Height of fractal.]:[height]
240[s:symbols?Symbols to build the fractal from.]:[symbolstring]
241[m:mag?Magnification level.]:[magnificationlevel]
242[p:stepwidth?Width per step.]:[widthperstep]
243[S:serial?Run in serial mode.]
244[P:parallel?Run in parallel mode.]
245[M:mode?Execution mode.]:[mode]
246[C:numcpus?Number of processors used for parallel execution.]:[numcpus]
247[+SEE ALSO?\bjuliaset1\b(1), \bksh93\b(1)]
248'
249
250while getopts -a "${progname}" "${mandelbrotset1_usage}" OPT ; do
251#	printmsg "## OPT=|${OPT}|, OPTARG=|${OPTARG}|"
252	case ${OPT} in
253		w)	m_width="${OPTARG}"	;;
254		h)	m_height="${OPTARG}"	;;
255		s)	symbollist="${OPTARG}"	;;
256		m)	max_mag="${OPTARG}"	;;
257		p)	stepwidth="${OPTARG}"	;;
258		S)	mode="serial"		;;
259		+S)	mode="parallel"		;;
260		P)	mode="parallel"		;;
261		+P)	mode="serial"		;;
262		M)	mode="${OPTARG}"	;;
263		C)	numcpus="${OPTARG}"	;;
264		*)	usage			;;
265	esac
266done
267shift $((OPTIND-1))
268
269printmsg "# width=${m_width}"
270printmsg "# height=${m_height}"
271printmsg "# max_mag=${max_mag}"
272printmsg "# stepwidth=${stepwidth}"
273printmsg "# symbollist='${symbollist}'"
274printmsg "# mode=${mode}"
275
276(( symbollistlen=${#symbollist}-1 ))
277
278((
279	x_max=m_width*stepwidth/2. ,
280	x_min=-x_max ,
281	y_max=m_height*stepwidth/2. ,
282	y_min=-y_max
283))
284
285case "${mode}" in
286	parallel)	loop_parallel	; exit $? ;;
287	serial)		loop_serial	; exit $? ;;
288	*)		fatal_error $"Unknown mode \"${mode}\"." ;;
289esac
290
291fatal_error "not reached."
292# EOF.
293