# STACK Question Profiler

This workbook contains functions for timing Maxima code, e.g. part of a particular question. 

This code is based on the sandbox.

You can copy and paste cas commands from the STACK dashboard. 

To intercept the raw CAS command created by STACK, insert `print_r($command); die();` after 
https://github.com/maths/moodle-qtype_stack/blob/a077a7dbb04e9e9fc032fdb70b3137325d1b6a0a/stack/cas/cassession2.class.php#L382

(Note that the above weblink is a permalink, so it will link to an older version of
STACK; the corresponding location in newer versions of STACK may be different.)

/* For MS platforms you normally need to explicitly set the path.
   This assumes you have cloned/downloaed the STACK code into c:/tmp/stackroot
   E.g. this file must exist c:/tmp/stackroot/stackmaxima.mac 

   Use the forward slash as a directory seperator.
   No trailing slash.
*/
maximaplatform:"win"$
stacklocation:"c:/tmp/stackroot"$
stacktmplocation:"c:/tmp"$

maximaplatform:"linux"$
stacklocation:"/var/www/html/moodle/question/type/stack"$
stacktmplocation:"~/tmp"$


/****************************************************
   There should be no need to edit below this line.  
   
   These commands add the location to Maxima's search path. 
*/
file_search_maxima:append( [sconcat(stacklocation, "/stack/maxima/###.{mac,mc}")] , file_search_maxima)$
file_search_lisp:append( [sconcat(stacklocation, "/stack/maxima/###.{lisp}")] , file_search_lisp)$
file_search_maxima:append( [sconcat(stacklocation, "/stack/maxima/contrib/###.{mac,mc}")] , file_search_maxima)$
file_search_lisp:append( [sconcat(stacklocation, "/stack/maxima/contrib/###.{lisp}")] , file_search_lisp)$
file_search_maxima:append( [sconcat(stacktmplocation, "/###.{mac,mc}")] , file_search_maxima)$
file_search_lisp:append( [sconcat(stacktmplocation, "/###.{lisp}")] , file_search_lisp)$

/* 
   The following command may be slightly different on your particular server.
   There is normally no need to change this.  You can see this function on the STACK healtcheck page (moodle admin access only).
*/
STACK_SETUP(ex):=block(
    MAXIMA_VERSION_NUM_EXPECTED:0,
    MAXIMA_PLATFORM:maximaplatform,
    maxima_tempdir:"",
    IMAGE_DIR:"",
    PLOT_SIZE:[450,300],
    PLOT_TERMINAL:"svg",
    PLOT_TERM_OPT:"dynamic font \",11\" linewidth 1.2",
    DEL_CMD:"rm",
    GNUPLOT_CMD:"gnuplot",
    MAXIMA_VERSION_EXPECTED:"default",
    URL_BASE:"!ploturl!",
    /* Define units available in STACK. */
    stack_unit_si_prefix_code:[y, z, a, f, p, n, u, m, c, d, da, h, k, M, G, T, P, E, Z, Y],
    stack_unit_si_prefix_multiplier:[10^-24, 10^-21, 10^-18, 10^-15, 10^-12, 10^-9, 10^-6, 10^-3, 10^-2, 10^-1, 10, 10^2, 10^3, 10^6, 10^9, 10^12, 10^15, 10^18, 10^21, 10^24],
    stack_unit_si_prefix_tex:["\\mathrm{y}", "\\mathrm{z}", "\\mathrm{a}", "\\mathrm{f}", "\\mathrm{p}", "\\mathrm{n}", "\\mu ", "\\mathrm{m}", "\\mathrm{c}", "\\mathrm{d}", "\\mathrm{da}", "\\mathrm{h}", "\\mathrm{k}", "\\mathrm{M}", "\\mathrm{G}", "\\mathrm{T}", "\\mathrm{P}", "\\mathrm{E}", "\\mathrm{Z}", "\\mathrm{Y}"],
    stack_unit_si_unit_code:[m, l, L, g, t, s, h, Hz, Bq, cd, N, Pa, cal, Cal, Btu, eV, J, W, Wh, A, ohm, C, V, F, S, Wb, T, H, Gy, rem, Sv, lx, lm, mol, M, kat, rad, sr, K, VA, eV, Ci],
    stack_unit_si_unit_conversions:[m, m^3/1000, m^3/1000, kg/1000, 1000*kg, s, s*3600, 1/s, 1/s, cd, (kg*m)/s^2, kg/(m*s^2), 4.2*J, 4200*J, 1055*J, 1.602177e-19*J, (kg*m^2)/s^2, (kg*m^2)/s^3, 3600*(kg*m^2)/s^2, A, (kg*m^2)/(s^3*A^2), s*A, (kg*m^2)/(s^3*A), (s^4*A^2)/(kg*m^2), (s^3*A^2)/(kg*m^2), (kg*m^2)/(s^2*A), kg/(s^2*A), (kg*m^2)/(s^2*A^2), m^2/s^2, 0.01*Sv, m^2/s^2, cd/m^2, cd, mol, mol/(m^3/1000), mol/s, rad, sr, K, (kg*m^2)/(s^3), 1.602176634E-19*J, Ci],
    stack_unit_si_unit_tex:["\\mathrm{m}", "\\mathrm{l}", "\\mathrm{L}", "\\mathrm{g}", "\\mathrm{t}", "\\mathrm{s}", "\\mathrm{h}", "\\mathrm{Hz}", "\\mathrm{Bq}", "\\mathrm{cd}", "\\mathrm{N}", "\\mathrm{Pa}", "\\mathrm{cal}", "\\mathrm{cal}", "\\mathrm{Btu}", "\\mathrm{eV}", "\\mathrm{J}", "\\mathrm{W}", "\\mathrm{Wh}", "\\mathrm{A}", "\\Omega", "\\mathrm{C}", "\\mathrm{V}", "\\mathrm{F}", "\\mathrm{S}", "\\mathrm{Wb}", "\\mathrm{T}", "\\mathrm{H}", "\\mathrm{Gy}", "\\mathrm{rem}", "\\mathrm{Sv}", "\\mathrm{lx}", "\\mathrm{lm}", "\\mathrm{mol}", "\\mathrm{M}", "\\mathrm{kat}", "\\mathrm{rad}", "\\mathrm{sr}", "\\mathrm{K}", "\\mathrm{VA}", "\\mathrm{eV}", "\\mathrm{Ci}"],
    stack_unit_other_unit_code:[min, amu, u, mmHg, bar, ha, cc, gal, mbar, atm, torr, rev, deg, rpm, au, Da, Np, B, dB, day, year, hp, in, ft, yd, mi, lb],
    stack_unit_other_unit_conversions:[s*60, amu, amu, 133.322387415*Pa, 10^5*Pa, 10^4*m^2, m^3*10^(-6), 3.785*l, 10^2*Pa, 101325*Pa, 101325/760*Pa, 2*pi*rad, pi*rad/180, pi*rad/(30*s), 149597870700*m, 1.660539040E-27*kg, Np, B, dB, 86400*s, 3.156e7*s, 746*W, in, 12*in, 36*in, 5280*12*in, 4.4482*N],
    stack_unit_other_unit_tex:["\\mathrm{min}", "\\mathrm{amu}", "\\mathrm{u}", "\\mathrm{mmHg}", "\\mathrm{bar}", "\\mathrm{ha}", "\\mathrm{cc}", "\\mathrm{gal}", "\\mathrm{mbar}", "\\mathrm{atm}", "\\mathrm{torr}", "\\mathrm{rev}", "\\mathrm{{}^{o}}", "\\mathrm{rpm}", "\\mathrm{au}", "\\mathrm{Da}", "\\mathrm{Np}", "\\mathrm{B}", "\\mathrm{dB}", "\\mathrm{day}", "\\mathrm{year}", "\\mathrm{hp}", "\\mathrm{in}", "\\mathrm{ft}", "\\mathrm{yd}", "\\mathrm{mi}", "\\mathrm{lb}"],
    true)$

/* Load the main libraries. */
load("stackmaxima.mac")$
load("stats")$
load("distrib")$
load("descriptive")$
alias(stack_include_contrib, load)$

print(sconcat("[ STACK-Maxima started, library version ", stackmaximaversion, " ]"))$

/* Optional but useful. */
display2d:true;
simp:true;
debug:true;

Replace everything after `command() :=` in the below cell with the command displayed on the screen 
after intercepting calls to CAS in the appropriate location. Specifically, within 
`question/type/stack/stack/cas/cassession2.class.php` immediately after https://github.com/maths/moodle-qtype_stack/blob/a077a7dbb04e9e9fc032fdb70b3137325d1b6a0a/stack/cas/cassession2.class.php#L382 
insert `print_r($command); die();`. Then load the question you wish to time, and the CAS call 
should display in the browser. Copy and paste that below after `command := `

command() := block([stack_seed], stack_randseed(10), stack_seed:10, texput_decimal("."), make_multsgn("dot"), make_complexJ("i"), make_arccos("cos-1"), make_logic("lang"), 
    sqrtdispflag:true, simp:true, assume_pos:false, assume_real:false, lmxchar:"[", _RESPONSE:["stack_map"], _VALUES:["stack_map"], _LATEX:["stack_map"], _DVALUES:["stack_map"], 
    _CS2v("__stackmaximaversion",stackmaximaversion), %stmt:"s0", _EC(errcatch((_EC(errcatch(first_year:1983),"/qv/1:1-39:2"),_EC(errcatch(num_years:30),"/qv/2:1-39:2"),
                _EC(errcatch(row_1:["year","month","sunh","sunh_sat"]),"/qv/3:1-39:2"),
                _EC(errcatch(months_text:["January","February","March","April","May","June","July","August","September","October","November","December"]),"/qv/4:1-39:2"),
                _EC(errcatch(month_ran:(%_C(rand),rand(12))+1),"/qv/5:1-39:2"),_EC(errcatch(month_ran_text:months_text[month_ran]),"/qv/6:1-39:2"),
                _EC(errcatch(month_means:[45.8,66.0,136.0,205.1,273.6,256.8,271.2,239.5,172.4,119.6,55.2,38.5]),"/qv/8:1-39:2"),
                _EC(errcatch(month_sds:[17.6,19.4,34.1,54.7,55.2,51.2,56.3,43.0,42.1,25.2,20.1,13.6]*0.5),"/qv/9:1-39:2"),
                _EC(errcatch(diff_means:[0.3596,-2.1915,-8.0770,-0.6041,-13.7988,-10.5792,-9.5156,-5.7240,-3.6311,-2.7347,-0.4352,2.7373]),"/qv/10:1-39:2"),
                _EC(errcatch(diff_sds:[6.87,8.78,18.43,12.49,13.25,12.64,8.54,10.58,9.86,8.56,6.33,7.22]),"/qv/11:1-39:2"),
                _EC(errcatch(month_data:(%_C(makelist),makelist((%_C(makelist),makelist([y,m,(%_C(float),float((%_C(quantile_normal),
                                                        quantile_normal((%_C(rand),rand(1.0)),month_means[m],month_sds[m])))),diff_means[m],diff_sds[m]],m,1,12)),y,first_year,first_year+num_years-1))),
                    "/qv/12:1-39:2"),_EC(errcatch(data_set:(%_C(apply),%_C(append),apply(append,month_data))),"/qv/13:1-39:2"),
                _EC(errcatch(data_set:(%_C(makelist),makelist((%_C(endcons),endcons(data_set[i][3]+(%_C(float),float((%_C(quantile_normal),
                                                    quantile_normal((%_C(rand),rand(1.0)),data_set[i][4],data_set[i][5])))),(%_C(rest),rest(data_set[i],-2)))),i,1,30*12))),"/qv/14:1-39:2"),
                _EC(errcatch(data_set_wheader:(%_C(append),append([row_1],data_set))),"/qv/15:1-39:2"),_EC(errcatch(station_mean:[0,0,0,0,0,0,0,0,0,0,0,0]),"/qv/17:1-39:2"),
                _EC(errcatch(satellite_mean:[0,0,0,0,0,0,0,0,0,0,0,0]),"/qv/18:1-39:2"),
                _EC(errcatch(for m:1 thru 12 do for r:1 thru 
                        (%_C(length),length(data_set)) do (if data_set[r][2] = m then station_mean[m]:station_mean[m]+data_set[r][3],if data_set[r][2] = m then 
                            satellite_mean[m]:satellite_mean[m]+data_set[r][4])),"/qv/20:1-39:2"),_EC(errcatch(bias:(%_C(makelist),
                            makelist(satellite_mean[i]-station_mean[i],i,1,12))),"/qv/24:1-39:2"),_EC(errcatch(bias:bias/num_years),"/qv/25:1-39:2"),
                _EC(errcatch(ta1:bias[month_ran]),"/qv/26:1-39:2"),_EC(errcatch(ta1:(%_C(float),float(ta1))),"/qv/27:1-39:2"),
                _EC(errcatch(ta1_dp:(%_C(dispdp),dispdp(ta1,2))),"/qv/28:1-39:2"),_EC(errcatch(abs_bias:(%_C(abs),abs(bias))),"/qv/29:1-39:2"),
                _EC(errcatch(min_bias:(%_C(lmin),lmin(abs_bias))),"/qv/30:1-39:2"),
                _EC(errcatch(min_bias_ind:(%_C(sublist_indices),sublist_indices(bias,lambda([x1],(%_C(abs),abs(x1)) = min_bias)))[1]),"/qv/31:1-39:2"),
                _EC(errcatch(ta2_correct:months_text[min_bias_ind]),"/qv/32:1-39:2"),_EC(errcatch(func(x):=(%_C(is),is(x = ta2_correct))),"/qv/33:1-39:2"),
                _EC(errcatch(ta2:(%_C(maplist),%_C(func),maplist(func,months_text))),"/qv/34:1-39:2"),
                _EC(errcatch(ta2:(%_C(zip_with),zip_with("[",months_text,ta2))),"/qv/35:1-39:2"),
                _EC(errcatch(max_bias:(%_C(lmax),lmax(abs_bias))),"/qv/37:1-39:2"),
                _EC(errcatch(max_bias_ind:(%_C(sublist_indices),sublist_indices(bias,lambda([x1],(%_C(abs),abs(x1)) = max_bias)))[1]),"/qv/38:1-39:2"),
                _EC(errcatch(ta2_wrong:months_text[max_bias_ind]),"/qv/39:1-39:2"),true)),"/qv"), %stmt:"s1", _EC(errcatch(__s1:ta1_dp),""), _CS2l("__s1",__s1), %stmt:"s2", 
    _EC(errcatch(__s2:ta2),""), _CS2l("__s2",__s2), %stmt:"s3", _EC(errcatch(__s3:castext_simplify(block(local(simp),["%root","//CT2S4//",block([_ct2_tmp],
                            _EC(errcatch(_ct2_tmp:data_set_wheader),"/qt/6:14-6:34"),string(_ct2_tmp)),"//CT2S5//",block([_ct2_tmp,_ct2_simp],_ct2_simp:simp,_ct2_tmp:"//CT2S6//",
                            _EC(errcatch(_ct2_tmp:month_ran_text),"/qt/28:39-28:57"),simp:false,_ct2_tmp:ct2_latex(_ct2_tmp,"i",_ct2_simp),simp:_ct2_simp,_ct2_tmp),"//CT2S7//"]))),"/qt"), 
    _CS2v("__s3",__s3), _CS2dvv("__s1",__s1), _CS2dvv("__s2",__s2), _RESPONSE:stackmap_set(_RESPONSE,"presentation",_LATEX), _RESPONSE:stackmap_set(_RESPONSE,"display",_DVALUES), _CS2out() )$ 

`time_command` times the above command in seconds.

time_command() := (
    timer(all),
    command(),
    simp:true,
    T:timer_info(),
    S:sublist(rest(args(T)),lambda([a], not(is(third(a)=0)))),
    S:sort(S, lambda([a,b],third(a)>third(b))),
    S[1][4]
)$

time_command();

`get_most_called_function()` returns `[f, number of calls, total time for all calls]` where `f` is the name of the most called function when running `command()`, all times are given in seconds.

get_most_called_function() := (
    timer(all),
    command(),
    simp:true,
    T:timer_info(),
    S:sublist(rest(args(T)),lambda([a], not(is(third(a)=0)))),
    S:sort(S, lambda([a,b],third(a)>third(b))),
    [S[2][1], S[2][3], S[2][4]]
)$

get_most_called_function();

`get_highest_time_per_call()` returns `[f, time per call, number of calls, total time]` where `f` is the name of the function with highest time/call ratio in `command()`, and all times are given in seconds.

get_highest_time_per_call() := (
    timer(all),
    command(),
    simp:true,
    T:timer_info(),
    S:sublist(rest(args(T)),lambda([a], not(is(third(a)=0)))),
    float_time(a):= if a=0 then 0 else first(args(a)),
    S:sort(S, lambda([a,b],float_time(second(a))>float_time(second(b)))),
    [S[3][1], S[3][2], S[3][3], S[3][4]]
)$

get_highest_time_per_call();