Skip to content

Commit 495e19b

Browse files
authored
Merge pull request #1734 from maths/iss1723
Support for Maxima 5.48.0 onwards: fixes #1723
2 parents 86bde5a + ee041e2 commit 495e19b

12 files changed

Lines changed: 196 additions & 56 deletions

doc/en/Developer/Development_track.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ We use the [github issue tracker](https://github.com/maths/moodle-qtype_stack/is
1010
Issues with [github milestone 4.13.0](https://github.com/maths/moodle-qtype_stack/issues?q=is%3Aissue+milestone%3A4.13.0) include
1111

1212
1. Remove all "cte" code from Maxima - mostly install.
13+
2. Support for Maxima 5.47.0, 5.48.0, and 5.49.0. This includes a fix for issue #1281 from 5.48.0.
1314

1415
--------------------------------------
1516
## Testing a node, not a whole tree

doc/en/Installation/index.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,17 @@ To check your version of maxima, run `maxima --version`. If Moodle is set up us
5353

5454
Alternatively, Maxima can also be run on a separate server via [GoeMaxima](https://github.com/mathinstitut/goemaxima) or [MaximaPool](https://github.com/maths/stack_util_maximapool).
5555

56-
Please note
56+
Please note:
57+
58+
* STACK has been tested on Maxima 5.49.0 and 5.48.0, and earlier versions.
59+
* As of April 2026, the distributed version of Maxima 5.47.0, e.g. bundled with Debian, is missing the Grobner package which is essential. You will need to compile Maxima from source.
60+
* Maxima 5.40.0 changed the way subscripts were displayed. We are no longer testing against versions before 5.40.0, so there may be inconsitencies with display of subscripts with other versions.
61+
62+
Old notes:
5763

5864
* Please avoid versions 5.37.x which are known to have a minor bug which affects STACK. In particular with `simp:false`, \(s^{-1}\) is transformed into \(1/s\). This apparently minor change makes it impossible to distinguish between the two forms. This causes all sorts of problems. Do not use Maxima 5.37.1 to 5.37.3.
5965
* Older versions of Maxima: in particular, Maxima 5.23.2 has some differences which result in \(1/\sqrt{x} \neq \sqrt{1/x}\), and similar problems. This means that we have an inconsistency between questions between versions of maxima. Of course, we can argue about which values of \(x\) make \(1/\sqrt{x} = \sqrt{1/x}\), but currently the unit tests and assumption is that these expressions should be considered to be algebraically equivalent! So, older versions of Maxima are not supported for a reason. Please test thoroughly if you try to use an older version, and expect some errors in the mathematical parts of the code.
6066
* If you install more than one version of Maxima then you will need to tell STACK which version to use. Otherwise just use the "default" option.
61-
* Maxima 5.40.0 changed the way subscripts were displayed. We are no longer testing against versions before 5.40.0, so there may be inconsitencies with display of subscripts with other versions.
6267

6368
Instructions for installing a more recent version of Maxima on CentOS 6 are available on the [Moodle forum](https://moodle.org/mod/forum/discuss.php?d=270956) (Oct 2014).
6469

settings.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,12 @@
131131
get_string('settingcasmaximaversion_desc', 'qtype_stack'),
132132
null,
133133
[
134-
'5.40.0' => '5.40.0', '5.41.0' => '5.41.0', '5.42.0' => '5.42.0',
135-
'5.42.1' => '5.42.1', '5.42.2' => '5.42.2',
136-
'5.43.0' => '5.43.0', '5.43.1' => '5.43.1', '5.43.2' => '5.43.2',
137-
'5.44.0' => '5.44.0', '5.46.0' => '5.46.0', '5.47.0' => '5.47.0',
138-
'default' => 'default',
134+
'5.40.0' => '5.40.0', '5.41.0' => '5.41.0', '5.42.0' => '5.42.0',
135+
'5.42.1' => '5.42.1', '5.42.2' => '5.42.2', '5.43.0' => '5.43.0',
136+
'5.43.1' => '5.43.1', '5.43.2' => '5.43.2', '5.44.0' => '5.44.0',
137+
'5.45.0' => '5.45.0', '5.46.0' => '5.46.0', '5.47.0' => '5.47.0',
138+
'5.48.0' => '5.48.0', '5.49.0' => '5.49.0',
139+
'default' => 'default',
139140
]
140141
));
141142

stack/cas/connectorhelper.class.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,14 @@ public static function stackmaxima_version_healthcheck() {
251251

252252
$usedversion = stack_string('healthchecksstackmaximatooold');
253253
foreach ($results as $result) {
254-
if ($result['key'] != '__stackmaximaversion') {
254+
if (array_key_exists('key', $result) && $result['key'] != '__stackmaximaversion') {
255255
continue;
256256
}
257257

258-
$usedversion = $result['value'];
258+
$usedversion = null;
259+
if (array_key_exists('value', $result)) {
260+
$usedversion = $result['value'];
261+
}
259262
if (self::$config->stackmaximaversion == $usedversion) {
260263
return [
261264
'healthchecksstackmaximaversionok',
@@ -338,7 +341,6 @@ public static function stackmaxima_genuine_connect() {
338341

339342
// Really make sure there is no cache.
340343
[$results, $debug] = self::stackmaxima_nocache_call($command);
341-
342344
$success = true;
343345
$message = [];
344346
if (empty($results)) {
@@ -376,7 +378,8 @@ public static function stackmaxima_genuine_connect() {
376378
'healthuncachedstack_CAS_versionnotchecked',
377379
['actual' => $maximaversionstr]
378380
);
379-
} else if ($result['value'] != '"' . $maximaversion . '"') {
381+
} else if (substr($result['value'], 1, strlen($maximaversion)) != $maximaversion) {
382+
// Compiling from source can give things like 5.47.0_dirty.
380383
$message[] = stack_string(
381384
'healthuncachedstack_CAS_version',
382385
['expected' => $maximaversion, 'actual' => $maximaversionstr]

stack/maxima/intervals.mac

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ realset_tex(ex) := block([_a, _b, _c],
143143
concat("{", tex1(_a), " \\in {",tex1(_b), "}}")
144144
)$
145145
texput(realset, realset_tex)$
146+
texput(%union, "\\bigcup ")$
146147

147148
/* Returns True if p is an element of A. False, otherwise: */
148149

@@ -827,7 +828,7 @@ stack_single_variable_solver(ex) := block([v, rs1, rs2],
827828
if is(ex={}) then return(none),
828829
if is(ex={v}) then return(all),
829830
if logic_edgep(ex) then return(ex),
830-
if is(equal(ex,oo(-inf,inf))) then return(all),
831+
if ev(is(ex=oo(-inf,inf)),simp) then return(all),
831832

832833
rs1:ex,
833834
rs2:false,
@@ -862,13 +863,13 @@ stack_single_variable_solver_rec(ex, v) := block([r0, r1, r2],
862863
if inequalityp(ex) then ex:ev(inequality_factor_solve(ex), simp),
863864

864865
if safe_op(ex)="%or" or safe_op(ex)="%and" then block(
865-
r0:maplist(lambda([ex2], stack_single_variable_solver_rec(ex2, v)), args(ex)),
866+
r0:ev(maplist(lambda([ex2], stack_single_variable_solver_rec(ex2, v)), args(ex)), simp),
866867
r1:ev(sublist(r0, realset_soft_p), simp),
867868
r2:ev(sublist(r0, lambda([ex2], not(realset_soft_p(ex2)))), simp)
868869
),
870+
869871
if safe_op(ex)="%or" then return(ev(apply("%or", append([interval_tidy(r1)], r2)), simp)),
870872
if safe_op(ex)="%and" then return(ev(apply("%and", append([interval_intersect_list(r1)], r2)), simp)),
871-
872873
return(ex)
873874
)$
874875

stack/maxima/stackmaxima.mac

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ load("stack_logic.lisp");
138138
load("Ksimplifier.lisp");
139139

140140
/* We don't want to allow people to put boxes round things. */
141-
box(ex) := ex;
141+
kill(box);
142142

143143
/* Although this works well in MAXIMA, |'s are not allowed in STACK */
144144
/* The heuristics to catch the various errors do not work, since | is symmetrical */
@@ -1033,13 +1033,24 @@ set_plot_option([gnuplot_default_term_command, ""]);
10331033
plot(ex, [ra]) := /*stack_web_plot*/
10341034
block([simp:true, tfn, tfnp1, tfnp2, tfnp3, afn, ufn, lvs, preamble, sysp, sysr,
10351035
filename, tn, alt, altc, alttext, ral, ralforbid, pltargs, plotfunmake, plotdebug,
1036-
plotgrid2d, size, psize, plot_size, plot_tags, stack_mtell_quiet, plotpid, margin],
1036+
plotgrid2d, plotbox, size, psize, plot_size, plot_tags, stack_mtell_quiet, plotpid, margin],
10371037
stack_mtell_quiet:true,
10381038
plotdebug: false,
10391039
/* Check for grid2d in the plotoptions. */
10401040
plotgrid2d: false,
10411041
if member(grid2d, ra) or member(STACKGRID, ra) then
10421042
plotgrid2d: true,
1043+
/*******************/
1044+
/* Check for box. */
1045+
plotbox:sublist(ra, lambda([ex], if listp(ex) then is(first(ex) = box) else false)),
1046+
if emptyp(plotbox) then plotbox:true else block(
1047+
ra:delete(first(plotbox), ra),
1048+
if not(second(first(plotbox))) then plotbox:false
1049+
),
1050+
/* For new versions. */
1051+
if member(nobox, ra) then plotbox:false,
1052+
if plotdebug then print("Should plot2d print a box? ", plotbox),
1053+
/************************************/
10431054
/* Arguments to plot must be lists. */
10441055
ral: sublist(ra, listp), /* The actual arguments used by plot. */
10451056
/* Check expressions to be plotted has/have only one variable. */
@@ -1127,6 +1138,9 @@ set output ", afn),
11271138
/* Add in the command for the grid. */
11281139
if plotgrid2d and MAXIMA_VERSION_NUM>34 then
11291140
ral: append(ral, [grid2d]),
1141+
/* Add in the command for the nobox. */
1142+
if not(plotbox) then if MAXIMA_VERSION_NUM>47 then
1143+
ral:append(ral, [nobox]) else ral:append(ral, [[box,false]]),
11301144
if plotdebug then print(preamble),
11311145
if PLOT_TERMINAL="svg" then set_plot_option([svg_file, afn]),
11321146
if PLOT_TERMINAL="svg" then
@@ -3446,15 +3460,15 @@ assess(ex1,ex2):=block([ret],
34463460
/* ****************************************************** */
34473461

34483462
/* A function to create a truth table of an expression ex. */
3449-
truth_table(ex) := block([exs, vars, tab, tt],
3463+
truth_table(ex) := block([exs, vars, tbl, tt],
34503464
vars: ev(sort(listofvars(ex)),simp),
34513465
if length(vars) > 5 then error("truth_table will only print with fewer than 6 variables."),
34523466
/* Store this variable to prevent 2^n re-evaluations of this function. */
34533467
exs: noun_logic_remove(ex),
3454-
tab: maplist(lambda([ex2], zip_with("=", vars, ex2)), truth_table_allvars(vars)),
3455-
tab: maplist(lambda([ex2], append(maplist('rhs, ex2), [ev(logic_simp(exs), ex2)])), tab),
3456-
tab: append([append(vars, [ex])], tab),
3457-
apply('table, tab)
3468+
tbl: maplist(lambda([ex2], zip_with("=", vars, ex2)), truth_table_allvars(vars)),
3469+
tbl: maplist(lambda([ex2], append(maplist('rhs, ex2), [ev(logic_simp(exs), ex2)])), tbl),
3470+
tbl: append([append(vars, [ex])], tbl),
3471+
apply('table, tbl)
34583472
)$
34593473

34603474
truth_table_allvars(l) := block(

tests/answertest_general_cas_test.php

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -525,12 +525,22 @@ public function test_stack_maxima_translate_algequiv_matrix(): void {
525525
$this->assertFalse($at->do_test());
526526
$this->assertEquals(0, $at->get_at_mark());
527527

528-
$fb = 'stack_trans(\'ATMatrix_wrongentries\' , ' .
528+
if ($this->adapt_to_new_maxima('5.48.0')) {
529+
$fb = 'stack_trans(\'ATMatrix_wrongentries\' , ' .
530+
'!quot!\[\left[\begin{array}{cc} 1 & 2 \\\\ {\color{red}{\underline{2}}} & 4 \end{array}\right]\]!quot! );';
531+
} else {
532+
$fb = 'stack_trans(\'ATMatrix_wrongentries\' , ' .
529533
'!quot!\[ \left[\begin{array}{cc} 1 & 2 \\\\ {\color{red}{\underline{2}}} & 4 \end{array}\right]\]!quot! );';
534+
}
530535
$this->assertEquals(stack_maxima_translate($fb), $at->get_at_feedback());
531536

532-
$fbt = 'The entries underlined in red below are those that are incorrect. ' .
537+
if ($this->adapt_to_new_maxima('5.48.0')) {
538+
$fbt = 'The entries underlined in red below are those that are incorrect. ' .
539+
'\[\left[\begin{array}{cc} 1 & 2 \\\\ {\color{red}{\underline{2}}} & 4 \end{array}\right]\]';
540+
} else {
541+
$fbt = 'The entries underlined in red below are those that are incorrect. ' .
533542
'\[ \left[\begin{array}{cc} 1 & 2 \\\\ {\color{red}{\underline{2}}} & 4 \end{array}\right]\]';
543+
}
534544
$this->assert_content_with_maths_equals($fbt, $at->get_at_feedback());
535545
}
536546

@@ -541,11 +551,21 @@ public function test_stack_maxima_int_feedback_1(): void {
541551
$this->assertFalse($at->do_test());
542552
$this->assertEquals(0, $at->get_at_mark());
543553

544-
$fbt = 'The derivative of your answer should be equal to the expression that you were asked to integrate, that was: '.
545-
'\[\frac{e^{5\cdot x+7}}{5}+\frac{\left(5\cdot e^7\cdot x-e^7\right) \cdot e^{5\cdot x}}{5}\] '.
546-
'In fact, the derivative of your answer, with respect to \(x\) is: '.
547-
'\[5\cdot e^{5\cdot x+7}+5\cdot \left(5\cdot e^7\cdot x-e^7\right) \cdot e^{5\cdot x}\] '.
548-
'so you must have done something wrong!';
554+
if ($this->adapt_to_new_maxima('5.48.0')) {
555+
$fbt = 'The derivative of your answer should be equal to the expression that you were ' .
556+
'asked to integrate, that was: ' .
557+
'\[\frac{e^{5\cdot x}\cdot \left(5\cdot e^7\cdot x-e^7\right)}{5}+\frac{e^{5\cdot x+7}}{5}\] '.
558+
'In fact, the derivative of your answer, with respect to \(x\) is: '.
559+
'\[5\cdot e^{5\cdot x}\cdot \left(5\cdot e^7\cdot x-e^7\right)+5\cdot e^{5\cdot x+7}\] '.
560+
'so you must have done something wrong!';
561+
} else {
562+
$fbt = 'The derivative of your answer should be equal to the expression that you were ' .
563+
'asked to integrate, that was: ' .
564+
'\[\frac{e^{5\cdot x+7}}{5}+\frac{\left(5\cdot e^7\cdot x-e^7\right) \cdot e^{5\cdot x}}{5}\] '.
565+
'In fact, the derivative of your answer, with respect to \(x\) is: '.
566+
'\[5\cdot e^{5\cdot x+7}+5\cdot \left(5\cdot e^7\cdot x-e^7\right) \cdot e^{5\cdot x}\] '.
567+
'so you must have done something wrong!';
568+
}
549569
$this->assert_content_with_maths_equals($fbt, $at->get_at_feedback());
550570
}
551571

@@ -556,10 +576,19 @@ public function test_stack_maxima_int_feedback_2(): void {
556576
$this->assertFalse($at->do_test());
557577
$this->assertEquals(0, $at->get_at_mark());
558578

559-
$fbt = 'The derivative of your answer should be equal to the expression that you were asked to integrate, that was: '.
560-
'\[x\cdot e^{5\cdot x+7}\] In fact, the derivative of your answer, with respect to \(x\) is: '.
561-
'\[5\cdot e^{5\cdot x+7}+5\cdot \left(5\cdot e^7\cdot x-e^7\right) \cdot e^{5\cdot x}\] '.
562-
'so you must have done something wrong!';
579+
if ($this->adapt_to_new_maxima('5.48.0')) {
580+
$fbt = 'The derivative of your answer should be equal to the expression that you ' .
581+
'were asked to integrate, that was: \[x\cdot e^{5\cdot x+7}\] ' .
582+
'In fact, the derivative of your answer, with respect to \(x\) is: ' .
583+
'\[5\cdot e^{5\cdot x}\cdot \left(5\cdot e^7\cdot x-e^7\right)+5\cdot e^{5\cdot x+7}\] '.
584+
'so you must have done something wrong!';
585+
} else {
586+
$fbt = 'The derivative of your answer should be equal to the expression that you ' .
587+
'were asked to integrate, that was: \[x\cdot e^{5\cdot x+7}\] ' .
588+
'In fact, the derivative of your answer, with respect to \(x\) is: ' .
589+
'\[5\cdot e^{5\cdot x+7}+5\cdot \left(5\cdot e^7\cdot x-e^7\right) \cdot e^{5\cdot x}\] ' .
590+
'so you must have done something wrong!';
591+
}
563592
$this->assert_content_with_maths_equals($fbt, $at->get_at_feedback());
564593
}
565594

tests/cassession2_test.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2197,7 +2197,11 @@ public function test_union_tex(): void {
21972197
'\left( 1,\, 2\right) \cup \left( 3,\, 4\right) \cup \left( 4,\, 5\right)',
21982198
];
21992199
$cases[] = ['%union(a,b+1,d)', 'a \cup \left(b+1\right) \cup d'];
2200-
$cases[] = ['%union({5,6})', '\left \{5 , 6 \right \}'];
2200+
if ($this->adapt_to_new_maxima('5.47.0')) {
2201+
$cases[] = ['%union({5,6})', '\bigcup \left(\left \{5 , 6 \right \}\right)'];
2202+
} else {
2203+
$cases[] = ['%union({5,6})', '\left \{5 , 6 \right \}'];
2204+
}
22012205

22022206
$cases[] = ['%intersection(a,b,c)', 'a \cap b \cap c'];
22032207
$cases[] = [
@@ -3305,7 +3309,13 @@ public function test_let_matrix(): void {
33053309
$session->instantiate();
33063310
$this->assertTrue($session->is_instantiated());
33073311
$p = $session->get_by_key('p');
3308-
$this->assertEquals('X^3-3*X^2+3*X-1', $p->get_value());
3312+
if ($this->adapt_to_new_maxima('5.47.0')) {
3313+
// Change in Maxima 5.48.0.
3314+
$this->assertEquals('X^3-3*X^2+3*X-I^3', $p->get_value());
3315+
} else {
3316+
// Known edge case.
3317+
$this->assertEquals('X^3-3*X^2+3*X-1', $p->get_value());
3318+
}
33093319
}
33103320

33113321
public function test_unary_minus_sort(): void {

tests/castext_test.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1453,9 +1453,6 @@ public function test_numerical_display_float_scientific(): void {
14531453
*/
14541454
public function test_numerical_display_float_scientific_small(): void {
14551455

1456-
// On old Maxima, you get back \(9.999999999999999e-7\).
1457-
$this->skip_if_old_maxima('5.32.1');
1458-
14591456
// For some reason 5.41.0 returns \(9.999999999999999e-7\) too.
14601457
$this->skip_if_new_maxima('5.40.0');
14611458

@@ -3139,7 +3136,7 @@ public function test_include_chemistry(): void {
31393136

31403137
$options = new stack_options();
31413138

3142-
$vars = 'stack_include("contribl://chemistry.mac");';
3139+
$vars = "stack_include_contrib(\"chemistry.mac\");";
31433140
$at1 = new stack_cas_keyval($vars, $options, 123);
31443141
$this->assertTrue($at1->get_valid());
31453142

0 commit comments

Comments
 (0)