1 /*
2  * fn-erlang.c:  Teletraffic functions.
3  *
4  * Authors:
5  *   Arief Mulya Utama <arief_m_utama@telkomsel.co.id>
6  *                     <arief.utama@gmail.com>
7  *   [Initial plugin]
8  *
9  * Morten Welinder <terra@gnome.org>
10  *   [calculate_loggos]
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, see <https://www.gnu.org/licenses/>.
24  */
25 #include <gnumeric-config.h>
26 #include <gnumeric.h>
27 #include <func.h>
28 #include <parse-util.h>
29 #include <cell.h>
30 #include <value.h>
31 #include <mathfunc.h>
32 #include <gnm-format.h>
33 #include <workbook.h>
34 #include <sheet.h>
35 #include <tools/goal-seek.h>
36 #include <gnm-i18n.h>
37 
38 #include <goffice/goffice.h>
39 #include <gnm-plugin.h>
40 
41 #include <math.h>
42 #include <string.h>
43 #include <stdlib.h>
44 
45 /*
46  * comp_gos == 1 - gos
47  */
48 static gnm_float
guess_carried_traffic(gnm_float traffic,gnm_float comp_gos)49 guess_carried_traffic (gnm_float traffic, gnm_float comp_gos)
50 {
51 	return traffic * comp_gos;
52 }
53 
54 static gnm_float
calculate_loggos(gnm_float traffic,gnm_float circuits)55 calculate_loggos (gnm_float traffic, gnm_float circuits)
56 {
57 	if (traffic < 0 || circuits < 1)
58 		return gnm_nan;
59 
60 	return (dgamma (traffic, circuits + 1, 1, TRUE) -
61 		pgamma (traffic, circuits + 1, 1, FALSE, TRUE));
62 }
63 
64 static gnm_float
calculate_gos(gnm_float traffic,gnm_float circuits,gboolean comp)65 calculate_gos (gnm_float traffic, gnm_float circuits, gboolean comp)
66 {
67 	gnm_float gos;
68 
69 	/* extra guards won't hurt, right? */
70 	if (circuits < 1 || traffic < 0)
71 		return -1;
72 
73 	if (traffic == 0)
74 		gos = comp ? 1 : 0;
75 	else if (circuits < 100) {
76 		gnm_float cir_iter = 1;
77 		gos = 1;
78 		for (cir_iter = 1; cir_iter <= circuits; cir_iter++)
79 			gos = (traffic * gos) / (cir_iter + (traffic * gos));
80 		if (comp) gos = 1 - gos;
81 	} else if (circuits / traffic < 0.9) {
82 		gnm_float sum = 0, term = 1, n = circuits;
83 		while (n > 1) {
84 			term *= n / traffic;
85 			if (term < GNM_EPSILON * sum)
86 				break;
87 			sum += term;
88 			n--;
89 		}
90 		gos = comp ? sum / (1 + sum) : 1 / (1 + sum);
91 	} else {
92 		gnm_float loggos = calculate_loggos (traffic, circuits);
93 		gos = comp ? -gnm_expm1 (loggos) : gnm_exp (loggos);
94 	}
95 
96 	return gos;
97 }
98 
99 GNM_PLUGIN_MODULE_HEADER;
100 
101 /***************************************************************************/
102 static GnmFuncHelp const help_probblock[] = {
103 	{ GNM_FUNC_HELP_NAME, F_("PROBBLOCK:probability of blocking")},
104 	{ GNM_FUNC_HELP_ARG, F_("traffic:number of calls")},
105 	{ GNM_FUNC_HELP_ARG, F_("circuits:number of circuits")},
106 	{ GNM_FUNC_HELP_DESCRIPTION, F_("PROBBLOCK returns probability of blocking when @{traffic}"
107 					" calls load into @{circuits} circuits.")},
108 	{ GNM_FUNC_HELP_NOTE, F_("@{traffic} cannot exceed @{circuits}.") },
109 	{ GNM_FUNC_HELP_EXAMPLES, "=PROBBLOCK(24,30)" },
110 	{ GNM_FUNC_HELP_SEEALSO, "OFFTRAF,DIMCIRC,OFFCAP"},
111 	{ GNM_FUNC_HELP_END }
112 };
113 
114 static GnmValue *
gnumeric_probblock(GnmFuncEvalInfo * ei,GnmValue const * const * argv)115 gnumeric_probblock (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
116 {
117 	gnm_float traffic  = value_get_as_float (argv[0]);
118 	gnm_float circuits = value_get_as_float (argv[1]);
119 	gnm_float gos = calculate_gos (traffic, circuits, FALSE);
120 
121 	if (gos >= 0)
122 		return value_new_float (gos);
123 	else
124 		return value_new_error_VALUE (ei->pos);
125 }
126 
127 static GnmFuncHelp const help_offtraf[] = {
128 	{ GNM_FUNC_HELP_NAME, F_("OFFTRAF:predicted number of offered calls")},
129 	{ GNM_FUNC_HELP_ARG, F_("traffic:number of carried calls")},
130 	{ GNM_FUNC_HELP_ARG, F_("circuits:number of circuits")},
131 	{ GNM_FUNC_HELP_DESCRIPTION, F_("OFFTRAF returns the predicted number of offered calls given @{traffic} carried calls (taken from measurements) on @{circuits} circuits.")},
132 	{ GNM_FUNC_HELP_NOTE, F_("@{traffic} cannot exceed @{circuits}.") },
133 	{ GNM_FUNC_HELP_EXAMPLES, "=OFFTRAF(24,30)" },
134 	{ GNM_FUNC_HELP_SEEALSO, "PROBBLOCK,DIMCIRC,OFFCAP"},
135 	{ GNM_FUNC_HELP_END }
136 };
137 
138 typedef struct {
139 	gnm_float traffic, circuits;
140 } gnumeric_offtraf_t;
141 
142 static GnmGoalSeekStatus
gnumeric_offtraf_f(gnm_float off_traffic,gnm_float * y,void * user_data)143 gnumeric_offtraf_f (gnm_float off_traffic, gnm_float *y, void *user_data)
144 {
145 	gnumeric_offtraf_t *pudata = user_data;
146 	gnm_float comp_gos = calculate_gos (off_traffic, pudata->circuits, TRUE);
147 	if (comp_gos < 0)
148 		return GOAL_SEEK_ERROR;
149 	*y = guess_carried_traffic (off_traffic, comp_gos) - pudata->traffic;
150 	return GOAL_SEEK_OK;
151 }
152 
153 static GnmValue *
gnumeric_offtraf(GnmFuncEvalInfo * ei,GnmValue const * const * argv)154 gnumeric_offtraf (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
155 {
156 	gnm_float traffic = value_get_as_float (argv[0]);
157 	gnm_float circuits = value_get_as_float (argv[1]);
158 	gnm_float traffic0;
159 	GnmGoalSeekData data;
160 	GnmGoalSeekStatus status;
161 	gnumeric_offtraf_t udata;
162 
163 	if (circuits < 1 || traffic < 0)
164 		return value_new_error_VALUE (ei->pos);
165 
166 	goal_seek_initialize (&data);
167 	data.xmin = traffic;
168 	data.xmax = circuits;
169 	udata.circuits = circuits;
170 	udata.traffic = traffic;
171 	traffic0 = (data.xmin + data.xmax) / 2;
172 	/* Newton search from guess.  */
173 	status = goal_seek_newton (&gnumeric_offtraf_f, NULL,
174 				   &data, &udata, traffic0);
175 	if (status != GOAL_SEEK_OK) {
176 		(void)goal_seek_point (&gnumeric_offtraf_f, &data, &udata, traffic);
177 		(void)goal_seek_point (&gnumeric_offtraf_f, &data, &udata, circuits);
178 		status = goal_seek_bisection (&gnumeric_offtraf_f, &data, &udata);
179 	}
180 
181 	if (status == GOAL_SEEK_OK)
182 		return value_new_float (data.root);
183 	else
184 		return value_new_error_VALUE (ei->pos);
185 }
186 
187 static GnmFuncHelp const help_dimcirc[] = {
188 	{ GNM_FUNC_HELP_NAME, F_("DIMCIRC:number of circuits required")},
189 	{ GNM_FUNC_HELP_ARG, F_("traffic:number of calls")},
190 	{ GNM_FUNC_HELP_ARG, F_("gos:grade of service")},
191 	{ GNM_FUNC_HELP_DESCRIPTION, F_("DIMCIRC returns the number of circuits required given @{traffic} calls with grade of service @{gos}.")},
192 	{ GNM_FUNC_HELP_EXAMPLES, "=DIMCIRC(24,0.01)" },
193 	{ GNM_FUNC_HELP_SEEALSO, "OFFCAP,OFFTRAF,PROBBLOCK"},
194 	{ GNM_FUNC_HELP_END }
195 };
196 
197 static GnmValue *
gnumeric_dimcirc(GnmFuncEvalInfo * ei,GnmValue const * const * argv)198 gnumeric_dimcirc (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
199 {
200 	gnm_float traffic  = value_get_as_float (argv[0]);
201 	gnm_float des_gos  = value_get_as_float (argv[1]);
202 	gnm_float low, high;
203 
204 	if (des_gos > 1 || des_gos <= 0)
205 		return value_new_error_VALUE (ei->pos);
206 
207 	low = high = 1;
208 	while (calculate_gos (traffic, high, FALSE) > des_gos) {
209 		low = high;
210 		high += high;
211 	}
212 
213 	while (high - low > 1.5) {
214 		gnm_float mid = gnm_floor ((high + low) / 2 + 0.1);
215 		gnm_float gos = calculate_gos (traffic, mid, FALSE);
216 		if (gos > des_gos)
217 			low = mid;
218 		else
219 			high = mid;
220 	}
221 
222 	return value_new_float (high);
223 }
224 
225 static GnmFuncHelp const help_offcap[] = {
226 	{ GNM_FUNC_HELP_NAME, F_("OFFCAP:traffic capacity")},
227 	{ GNM_FUNC_HELP_ARG, F_("circuits:number of circuits")},
228 	{ GNM_FUNC_HELP_ARG, F_("gos:grade of service")},
229 	{ GNM_FUNC_HELP_DESCRIPTION, F_("OFFCAP returns the traffic capacity given @{circuits} circuits with grade of service @{gos}.")},
230 	{ GNM_FUNC_HELP_EXAMPLES, "=OFFCAP(30,0.01)" },
231 	{ GNM_FUNC_HELP_SEEALSO, "DIMCIRC,OFFTRAF,PROBBLOCK"},
232 	{ GNM_FUNC_HELP_END }
233 };
234 
235 typedef struct {
236 	gnm_float circuits, des_gos;
237 } gnumeric_offcap_t;
238 
239 static GnmGoalSeekStatus
gnumeric_offcap_f(gnm_float traffic,gnm_float * y,void * user_data)240 gnumeric_offcap_f (gnm_float traffic, gnm_float *y, void *user_data)
241 {
242 	gnumeric_offcap_t *pudata = user_data;
243 	gnm_float gos = calculate_gos (traffic, pudata->circuits, FALSE);
244 	if (gos < 0)
245 		return GOAL_SEEK_ERROR;
246 	*y = gos - pudata->des_gos;
247 	return GOAL_SEEK_OK;
248 }
249 
250 static GnmValue *
gnumeric_offcap(GnmFuncEvalInfo * ei,GnmValue const * const * argv)251 gnumeric_offcap (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
252 {
253 	gnm_float circuits = value_get_as_float (argv[0]);
254 	gnm_float des_gos  = value_get_as_float (argv[1]);
255 	gnm_float traffic0;
256 	GnmGoalSeekData data;
257 	GnmGoalSeekStatus status;
258 	gnumeric_offcap_t udata;
259 
260 	if (des_gos >= 1 || des_gos <= 0)
261 		return value_new_error_VALUE (ei->pos);
262 
263 	goal_seek_initialize (&data);
264 	data.xmin = 0;
265 	data.xmax = circuits / (1 - des_gos);
266 	udata.circuits = circuits;
267 	udata.des_gos = des_gos;
268 
269 	traffic0 = data.xmax * (2 + des_gos * 10) / (3 + des_gos * 10);
270 	/* Newton search from guess.  */
271 	status = goal_seek_newton (&gnumeric_offcap_f, NULL,
272 				   &data, &udata, traffic0);
273 	if (status != GOAL_SEEK_OK) {
274 		(void)goal_seek_point (&gnumeric_offcap_f, &data, &udata, data.xmin);
275 		(void)goal_seek_point (&gnumeric_offcap_f, &data, &udata, data.xmax);
276 		status = goal_seek_bisection (&gnumeric_offcap_f, &data, &udata);
277 	}
278 
279 	if (status == GOAL_SEEK_OK)
280 		return value_new_float (data.root);
281 	else
282 		return value_new_error_VALUE (ei->pos);
283 }
284 
285 GnmFuncDescriptor const erlang_functions[] = {
286 	{ "probblock",        "ff",   help_probblock,
287 	  gnumeric_probblock, NULL,
288 	  GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_PERCENT,
289 	  GNM_FUNC_IMPL_STATUS_UNIQUE_TO_GNUMERIC,
290 	  GNM_FUNC_TEST_STATUS_NO_TESTSUITE },
291 	{ "offtraf",        "ff",   help_offtraf,
292 	  gnumeric_offtraf, NULL,
293 	  GNM_FUNC_SIMPLE,
294 	  GNM_FUNC_IMPL_STATUS_UNIQUE_TO_GNUMERIC,
295 	  GNM_FUNC_TEST_STATUS_NO_TESTSUITE },
296 	{ "dimcirc",        "ff",   help_dimcirc,
297 	  gnumeric_dimcirc, NULL,
298 	  GNM_FUNC_SIMPLE,
299 	  GNM_FUNC_IMPL_STATUS_UNIQUE_TO_GNUMERIC,
300 	  GNM_FUNC_TEST_STATUS_NO_TESTSUITE },
301 	{ "offcap",        "ff",   help_offcap,
302 	  gnumeric_offcap, NULL,
303 	  GNM_FUNC_SIMPLE,
304 	  GNM_FUNC_IMPL_STATUS_UNIQUE_TO_GNUMERIC,
305 	  GNM_FUNC_TEST_STATUS_NO_TESTSUITE },
306         {NULL}
307 };
308