From 39d3e454250301cfbfe46f73323460c5ffdf9595 Mon Sep 17 00:00:00 2001 From: Michael Gage Date: Mon, 26 Jun 2017 22:40:02 -0400 Subject: [PATCH 01/70] Cleaned up the sage file. It still needs more testing. --- macros/sage.pl | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/macros/sage.pl b/macros/sage.pl index dd56acc047..1f92568a77 100644 --- a/macros/sage.pl +++ b/macros/sage.pl @@ -22,7 +22,7 @@ sub new { SageAnswerName => 'sageAnswer', # not used yet SageAnswerValue => 'ansList', # not used yet AutoEvaluateCell => 'true', - ShowAnswerBlank => 'hidden', + ShowAnswerBlank => 'hidden', #'hidden', accepted_tos =>'false', # force author to accept terms of service explicitly @_ ); @@ -31,8 +31,34 @@ sub new { %options }, $class; - main::RECORD_ANS_NAME($self->{SageAnswerName}, 345); - + # main::RECORD_ANS_NAME($self->{SageAnswerName}, 345); + my $recordAnswerBlank=''; + if ($options{ShowAnswerBlank} eq 'visible') { + $recordAnswerBlank= main::NAMED_ANS_RULE($self->{SageAnswerName},15); + } elsif ($options{ShowAnswerBlank} eq 'hidden') { + $recordAnswerBlank= main::NAMED_HIDDEN_ANS_RULE($self->{SageAnswerName},15); + } else { + main::WARN_MESSAGE("Option $option{ShowAnswerBlank} is not valid for displaying sage answer rule. "); + } + # you could add an option to print an ANSWER BOX instead of an ANSWER RULE + $sage::recordAnswerString = <{SageAnswerValue}): +# # pretty_print( HtmlFragment('{ShowAnswerBlank} size=15 name="$self->{SageAnswerName}" id="$self->{SageAnswerName}" value="%s">'%($self->{SageAnswerValue},) ) ) + + $self->sageCode(); $self->sagePrint(); return $self; @@ -42,13 +68,15 @@ sub new { sub sageCode{ my $self = shift; main::TEXT(main::MODES(TeX=>"", HTML=><<"SAGE_CODE")); +
- From 56f772a6ae403696a7b08aaac73d2647d41cfb91 Mon Sep 17 00:00:00 2001 From: Michael Gage Date: Thu, 6 Jul 2017 21:44:23 -0400 Subject: [PATCH 02/70] changes to sage.pl to accamodate the new way of handling html fragments --- macros/sage.pl | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/macros/sage.pl b/macros/sage.pl index 1f92568a77..e48d33f4c4 100644 --- a/macros/sage.pl +++ b/macros/sage.pl @@ -23,6 +23,7 @@ sub new { SageAnswerValue => 'ansList', # not used yet AutoEvaluateCell => 'true', ShowAnswerBlank => 'hidden', #'hidden', + AnswerReturn => 1, # 0 means no answer blank is registered accepted_tos =>'false', # force author to accept terms of service explicitly @_ ); @@ -30,13 +31,17 @@ sub new { $self = bless { %options }, $class; - - # main::RECORD_ANS_NAME($self->{SageAnswerName}, 345); + $options{ShowAnswerBlank} = 'none' if $options{AnswerReturn} == 0; + # main::RECORD_ANS_NAME($self->{SageAnswerName}, 345); -- old version of code + # the following options register a named answer blank + # and produce the HTML text for embedding it. my $recordAnswerBlank=''; if ($options{ShowAnswerBlank} eq 'visible') { $recordAnswerBlank= main::NAMED_ANS_RULE($self->{SageAnswerName},15); } elsif ($options{ShowAnswerBlank} eq 'hidden') { $recordAnswerBlank= main::NAMED_HIDDEN_ANS_RULE($self->{SageAnswerName},15); + } elsif ($options{ShowAnswerBlank} eq 'none') { + $recordAnswerBlank=''; # don't register an answer blank } else { main::WARN_MESSAGE("Option $option{ShowAnswerBlank} is not valid for displaying sage answer rule. "); } From db091bed8201691713369a8be9e4a54167301c7b Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Thu, 20 Jul 2017 11:49:36 -0700 Subject: [PATCH 03/70] add mph to units --- lib/Units.pm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Units.pm b/lib/Units.pm index 0a076c5c83..61755781b4 100644 --- a/lib/Units.pm +++ b/lib/Units.pm @@ -255,6 +255,11 @@ our %known_units = ('m' => { 'm' => 1, 's' => -1 }, + 'mph' => { + 'factor' => 0.44704, + 'm' => 1, + 's' => -1 + }, # MASS # mg -- miligrams # g -- grams From c403519da54a634fbae44919c3c31950a3535981 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Thu, 20 Jul 2017 11:52:36 -0700 Subject: [PATCH 04/70] add % to units --- lib/Units.pm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Units.pm b/lib/Units.pm index 61755781b4..52907bb822 100644 --- a/lib/Units.pm +++ b/lib/Units.pm @@ -97,6 +97,9 @@ our %known_units = ('m' => { 'factor' => 1, 'cd' => 1, }, + '%' => { + 'factor' => 0.01, + }, # ANGLES # deg -- degrees # sr -- steradian, a mesure of solid angle From e9fc620835d538ed12392b51e50554344f8c034f Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Thu, 20 Jul 2017 12:00:31 -0700 Subject: [PATCH 05/70] add full word options for some units --- lib/Units.pm | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/lib/Units.pm b/lib/Units.pm index 0a076c5c83..3d0c34ed53 100644 --- a/lib/Units.pm +++ b/lib/Units.pm @@ -105,7 +105,23 @@ our %known_units = ('m' => { 'factor' => 0.0174532925, 'rad' => 1 }, - 'sr' => { + 'degree' => { + 'factor' => 0.0174532925, + 'rad' => 1 + }, + 'degrees' => { + 'factor' => 0.0174532925, + 'rad' => 1 + },1 + 'radian' => { + 'factor' => 1, + 'rad' => 1 + }, + 'radians' => { + 'factor' => 1, + 'rad' => 1 + }, + 'sr' => { 'factor' => 1, 'rad' => 2 }, @@ -126,10 +142,30 @@ our %known_units = ('m' => { 'factor' => 60, 's' => 1 }, + 'minute' => { + 'factor' => 60, + 's' => 1 + }, + 'minutes' => { + 'factor' => 60, + 's' => 1 + }, 'hr' => { 'factor' => 3600, 's' => 1 }, + 'hour' => { + 'factor' => 3600, + 's' => 1 + }, + 'hours' => { + 'factor' => 3600, + 's' => 1 + }, + 'h' => { + 'factor' => 3600, + 's' => 1 + }, 'day' => { 'factor' => 86400, 's' => 1 @@ -194,10 +230,26 @@ our %known_units = ('m' => { 'factor' => 0.0254, 'm' => 1 }, + 'inch' => { + 'factor' => 0.0254, + 'm' => 1 + }, + 'inches' => { + 'factor' => 0.0254, + 'm' => 1 + }, 'ft' => { 'factor' => 0.3048, 'm' => 1 }, + 'feet' => { + 'factor' => 0.3048, + 'm' => 1 + }, + 'foot' => { + 'factor' => 0.3048, + 'm' => 1 + }, 'mi' => { 'factor' => 1609.344, 'm' => 1 @@ -237,10 +289,18 @@ our %known_units = ('m' => { 'factor' => 1E-6, 'm' => 3, }, - 'dL' => { + 'dL' => { 'factor' => 0.0001, 'm' => 3 }, + 'cup' => { + 'factor' => 0.000236588, + 'm' => 3 + }, + 'cups' => { + 'factor' => 0.000236588, + 'm' => 3 + }, # VELOCITY # knots -- nautical miles per hour # c -- speed of light From 27e1f7cfd29871ad900d23915153958605e970e1 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Thu, 20 Jul 2017 14:18:29 -0700 Subject: [PATCH 06/70] allow omission of width from image subroutine and preserve aspect ratio --- macros/PGbasicmacros.pl | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/macros/PGbasicmacros.pl b/macros/PGbasicmacros.pl index 7f2ce4847b..8edbcce3d9 100644 --- a/macros/PGbasicmacros.pl +++ b/macros/PGbasicmacros.pl @@ -2689,7 +2689,7 @@ sub image { my %in_options = @opt; my %known_options = ( width => 100, - height => 100, + height => '', tex_size => 800, extra_html_tags => '', ); @@ -2709,6 +2709,11 @@ sub image { my $width_ratio = $tex_size*(.001); my @image_list = (); + # if height was explicitly given, create string for height attribute to be used in HTML, LaTeX2HTML + # otherwise omit a height attribute and allow the browser to use aspect ratio preservation + my $height_attrib = ''; + $height_attrib = qq{height = "$height"} if ($height); + if (ref($image_ref) =~ /ARRAY/ ) { @image_list = @{$image_ref}; } else { @@ -2741,7 +2746,7 @@ sub image { } } elsif ($displayMode eq 'Latex2HTML') { my $wid = ($envir->{onTheFlyImageSize} || 0)+ 30; - $out = qq!\\begin{rawhtml}\n\n + $out = qq!\\begin{rawhtml}\n\n \\end{rawhtml}\n ! } elsif ($displayMode eq 'HTML_MathJax' || $displayMode eq 'HTML_dpng' @@ -2754,7 +2759,7 @@ sub image { my $wid = ($envir->{onTheFlyImageSize} || 0) +30; $out = qq! - + ! } else { From 0413befb96a77b92f5c52c26bfddecad5352c3a6 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Thu, 20 Jul 2017 16:00:56 -0700 Subject: [PATCH 07/70] code typo --- lib/Units.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Units.pm b/lib/Units.pm index 3d0c34ed53..736332da49 100644 --- a/lib/Units.pm +++ b/lib/Units.pm @@ -112,7 +112,7 @@ our %known_units = ('m' => { 'degrees' => { 'factor' => 0.0174532925, 'rad' => 1 - },1 + }, 'radian' => { 'factor' => 1, 'rad' => 1 From 7e91f69b437d17f8409ce934d1d9debe1aac9d67 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Wed, 2 Aug 2017 06:58:11 -0400 Subject: [PATCH 08/70] Fix problem with using identity matrix as a constant --- lib/Value/Matrix.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Value/Matrix.pm b/lib/Value/Matrix.pm index f2253c72a7..f230931256 100644 --- a/lib/Value/Matrix.pm +++ b/lib/Value/Matrix.pm @@ -321,9 +321,10 @@ sub I { Value::Error("You must provide a dimension for the Identity matrix") unless defined $d; Value::Error("Dimension must be a positive integer") unless $d =~ m/^[1-9]\d*$/; my @M = (); my @Z = split('',0 x $d); + my $REAL = $context->Package('Real'); foreach my $i (0..$d-1) { my @row = @Z; $row[$i] = 1; - push(@M,$self->make($context,@row)); + push(@M,$self->make($context, map {$REAL->new($_)} @row)); } return $self->make($context,@M); } From ec951d6eac714fcf41f5847c0e64bf372e434316 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Wed, 2 Aug 2017 07:14:49 -0400 Subject: [PATCH 09/70] Add missing parentheses around vectors in IJK mode. --- lib/Parser/List/Vector.pm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/Parser/List/Vector.pm b/lib/Parser/List/Vector.pm index 6b2458a33b..99378494d4 100644 --- a/lib/Parser/List/Vector.pm +++ b/lib/Parser/List/Vector.pm @@ -27,6 +27,7 @@ sub _check { sub ijk { my $self = shift; my $context = $self->context; my $method = shift || ($context->flag("StringifyAsTeX") ? 'TeX': 'string'); + my $precedence = shift || 0; my @coords = @{$self->{coords}}; $self->Error("Method 'ijk' can only be used on vectors in three-space") unless (scalar(@coords) <= 3); @@ -47,19 +48,20 @@ sub ijk { } } $string = $ijk[3] if $string eq ''; + $string = '('.$string.')' if $string =~ m/[-+]/ && $precedence > $context->operators->get('+')->{precedence}; return $string; } sub TeX { my $self = shift; - return $self->ijk("TeX") + return $self->ijk("TeX",@_) if $self->{ijk} || $self->{equation}{ijk} || $self->{equation}{context}->flag("ijk"); return $self->SUPER::TeX; } sub string { my $self = shift; - return $self->ijk("string") + return $self->ijk("string",@_) if $self->{ijk} || $self->{equation}{ijk} || $self->{equation}{context}->flag("ijk"); return $self->SUPER::string; } From db2b07f58232f999659a5753ab07f55a9b4b78cb Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Wed, 2 Aug 2017 07:26:57 -0400 Subject: [PATCH 10/70] Use proper closing markers for inequalities using 'and x != ...' --- macros/contextInequalities.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/macros/contextInequalities.pl b/macros/contextInequalities.pl index 983aed14fd..00d98f35cb 100644 --- a/macros/contextInequalities.pl +++ b/macros/contextInequalities.pl @@ -696,6 +696,7 @@ sub display { push(@points,$X.$ne.$x->{data}[0]->$method($equation)); $interval = $interval->with(isCopy=>1, data=>[$interval->value]) unless $interval->{isCopy}; $interval->{data}[1] = $x->{data}[1]; + $interval->{close} = $x->{close}; $interval->{rightInfinite} = 1 if $x->{rightInfinite}; next; } From f5957ff62066656243a0a1e044fd40176af05588 Mon Sep 17 00:00:00 2001 From: Michael Gage Date: Wed, 2 Aug 2017 08:09:44 -0400 Subject: [PATCH 11/70] update version number to develop --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 2fe7285f81..1cb0ed472f 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ -$PG_VERSION ='PG-2.13'; +$PG_VERSION ='develop'; $PG_COPYRIGHT_YEARS = '1996-2017'; 1; From 1655360f965f356d2777e9505d2b0ef8f97261e0 Mon Sep 17 00:00:00 2001 From: Florian Heiderich Date: Wed, 9 Aug 2017 18:28:39 +0200 Subject: [PATCH 12/70] introduce BLTR and ELTR for "left to right" environments Purpose: In HTML pages with global dir="rtl" setting (for instance in an arabic or hebrew environment) one may still want mathematical formulas to be printed from left to right. --- macros/PGbasicmacros.pl | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/macros/PGbasicmacros.pl b/macros/PGbasicmacros.pl index 8edbcce3d9..b112cd3748 100644 --- a/macros/PGbasicmacros.pl +++ b/macros/PGbasicmacros.pl @@ -77,6 +77,8 @@ BEGIN $EUL, $BCENTER, $ECENTER, + $BLTR, + $ELTR, $HR, $LBRACE, $RBRACE, @@ -144,6 +146,8 @@ sub _PGbasicmacros_init { $main::EUL = EUL(); $main::BCENTER = BCENTER(); $main::ECENTER = ECENTER(); + $main::BLTR = BLTR(); + $main::ELTR = ELTR(); $main::HR = HR(); $main::LBRACE = LBRACE(); $main::RBRACE = RBRACE(); @@ -197,6 +201,8 @@ sub _PGbasicmacros_init { $EUL = EUL(); $BCENTER = BCENTER(); $ECENTER = ECENTER(); + $BLTR = BLTR(); + $ELTR = ELTR(); $HR = HR(); $LBRACE = LBRACE(); $RBRACE = RBRACE(); @@ -1512,6 +1518,8 @@ =head2 Display constants $EUL EUL() end underlined type $BCENTER BCENTER() begin centered environment $ECENTER ECENTER() end centered environment + $BLTR BLTR() begin left to right environment + $ELTR ELTR() end left to right environment $HR HR() horizontal rule $LBRACE LBRACE() left brace $LB LB () left brace @@ -1580,8 +1588,10 @@ sub ALPHABET { sub EITALIC { MODES(TeX => '} ', Latex2HTML => '} ', HTML => ''); }; sub BUL { MODES(TeX => '\\underline{', Latex2HTML => '\\underline{', HTML => ''); }; sub EUL { MODES(TeX => '}', Latex2HTML => '}', HTML => ''); }; -sub BCENTER { MODES(TeX => '\\begin{center} ', Latex2HTML => ' \\begin{rawhtml}
\\end{rawhtml} ', HTML => '
'); }; +sub BCENTER { MODES(TeX => '\\begin{center} ', Latex2HTML => ' \\begin{rawhtml}
\\end{rawhtml} ', HTML => '
'); }; sub ECENTER { MODES(TeX => '\\end{center} ', Latex2HTML => ' \\begin{rawhtml}
\\end{rawhtml} ', HTML => '
'); }; +sub BLTR { MODES(TeX => ' ', Latex2HTML => ' \\begin{rawhtml}
\\end{rawhtml} ', HTML => ''); }; +sub ELTR { MODES(TeX => ' ', Latex2HTML => ' \\begin{rawhtml}
\\end{rawhtml} ', HTML => ''); }; sub HR { MODES(TeX => '\\par\\hrulefill\\par ', Latex2HTML => '\\begin{rawhtml}
\\end{rawhtml}', HTML => '
'); }; sub LBRACE { MODES( TeX => '\{', Latex2HTML => '\\lbrace', HTML => '{' , HTML_tth=> '\\lbrace' ); }; sub RBRACE { MODES( TeX => '\}', Latex2HTML => '\\rbrace', HTML => '}' , HTML_tth=> '\\rbrace',); }; From 69164ee008fa95c7b0b22645fcd64125157090c0 Mon Sep 17 00:00:00 2001 From: Michael Gage Date: Fri, 25 Aug 2017 11:02:52 -0400 Subject: [PATCH 13/70] Modify warning messages for extending answer rules and for persistent storage --- lib/PGcore.pm | 2 +- macros/PGbasicmacros.pl | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/PGcore.pm b/lib/PGcore.pm index cc041d9fe1..7db209cad4 100755 --- a/lib/PGcore.pm +++ b/lib/PGcore.pm @@ -527,7 +527,7 @@ sub store_persistent_data { # will store strings only (so far) my $self = shift; my $label = shift; my @content = @_; - $self->internal_debug_message("PGcore::store_persistent_data: storing $label in PERSISTENCE_HASH"); + # $self->internal_debug_message("PGcore::store_persistent_data: storing $label in PERSISTENCE_HASH"); if (defined($self->{PERSISTENCE_HASH}->{$label}) ) { warn "can' overwrite $label in persistent data"; } else { diff --git a/macros/PGbasicmacros.pl b/macros/PGbasicmacros.pl index 8edbcce3d9..20727e9271 100644 --- a/macros/PGbasicmacros.pl +++ b/macros/PGbasicmacros.pl @@ -457,11 +457,17 @@ sub NAMED_ANS_RULE_EXTENSION { } else { $label = generate_aria_label($name); } - # this is the name of the parent answer group + # $answer_group_name is the name of the parent answer group + # the group name is usually the same as the answer blank name + # when there is only one answer blank. + + + my $answer_group_name = $options{answer_group_name}//''; unless ($answer_group_name) { WARN_MESSAGE("Error in NAMED_ANSWER_RULE_EXTENSION: every call to this subroutine needs - to have \$options{answer_group_name} defined. Answer blank name: $name"); + to have \$options{answer_group_name} defined. For a single answer blank this is + usually the same as the answer blank name. Answer blank name: $name"); } # warn "from named answer rule extension in PGbasic answer_group_name: |$answer_group_name|"; my $answer_value = ''; From 21cb6d255e0125f0fa39615ddfa94dc679a2e8bb Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Thu, 7 Sep 2017 05:47:28 -0400 Subject: [PATCH 14/70] Add 'simplified' to messages referring to polynomials that are only true for simplified ones. --- macros/contextLimitedPolynomial.pl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/macros/contextLimitedPolynomial.pl b/macros/contextLimitedPolynomial.pl index 22ab7e717a..86b4c15333 100644 --- a/macros/contextLimitedPolynomial.pl +++ b/macros/contextLimitedPolynomial.pl @@ -129,7 +129,7 @@ sub _check { return; } return if $self->checkPolynomial; - $self->Error("Your answer doesn't look like a polynomial"); + $self->Error("Your answer doesn't look like a simplified polynomial"); } # @@ -151,7 +151,7 @@ sub checkExponents { if ($l->{exponents}) { my $single = $self->context->flag('singlePowers'); foreach my $i (0..scalar(@{$exponents})-1) { - $self->Error("A variable can appear only once in each term of a polynomial") + $self->Error("A variable can appear only once in each term of a simplified polynomial") if $exponents->[$i] && $l->{exponents}[$i] && $single; $exponents->[$i] += $l->{exponents}[$i]; } @@ -244,7 +244,7 @@ sub checkPolynomial { sub checkStrict { my $self = shift; - $self->Error("You can only use '%s' between coefficents and variables in a polynomial",$self->{bop}); + $self->Error("You can only use '%s' between coefficents and variables in a simplified polynomial",$self->{bop}); } ############################################## @@ -257,7 +257,7 @@ sub checkPolynomial { my ($l,$r) = ($self->{lop},$self->{rop}); $self->Error("In a polynomial, you can only divide by numbers") unless LimitedPolynomial::isConstant($r); - $self->Error("You can only divide a single term by a number") + $self->Error("You can only divide a single term by a number in a simplified polynomial") if $l->{isPoly} && $l->{isPoly} != 2; $self->{isPoly} = $l->{isPoly}; @@ -279,7 +279,7 @@ package LimitedPolynomial::BOP::power; sub checkPolynomial { my $self = shift; my ($l,$r) = ($self->{lop},$self->{rop}); - $self->Error("You can only raise a variable to a power in a polynomial") + $self->Error("You can only raise a variable to a power in a simplifiedpolynomial") unless $l->class eq 'Variable'; $self->Error("Exponents must be constant in a polynomial") unless LimitedPolynomial::isConstant($r); @@ -297,7 +297,7 @@ sub checkPolynomial { sub checkStrict { my $self = shift; - $self->Error("You can only use powers of a variable in a polynomial"); + $self->Error("You can only use powers of a variable in a simplified polynomial"); } ############################################## From 70e15094ac304696578f1441467b71f133d3b670 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Sat, 30 Sep 2017 15:23:21 -0700 Subject: [PATCH 15/70] protect percent signs in TeX for NumberWithUnits --- lib/Parser/Legacy/NumberWithUnits.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Parser/Legacy/NumberWithUnits.pm b/lib/Parser/Legacy/NumberWithUnits.pm index 22052634fa..80dae5dc0a 100644 --- a/lib/Parser/Legacy/NumberWithUnits.pm +++ b/lib/Parser/Legacy/NumberWithUnits.pm @@ -133,12 +133,14 @@ sub getUnits { # # Convert units to TeX format # (fix superscripts, put terms in \rm, +# escape percent, # and make a \frac out of fractions) # sub TeXunits { my $units = shift; $units =~ s/\^\(?([-+]?\d+)\)?/^{$1}/g; $units =~ s/\*/\\,/g; + $units =~ s/%/\\%/g; return '{\rm '.$units.'}' unless $units =~ m!^(.*)/(.*)$!; my $displayMode = WeBWorK::PG::Translator::PG_restricted_eval(q!$main::displayMode!); return '{\textstyle\frac{'.$1.'}{'.$2.'}}' if ($displayMode eq 'HTML_tth'); From 209846ac134f83cd5834a12663ff3f4c4e4c8ee5 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Sat, 30 Sep 2017 15:51:19 -0700 Subject: [PATCH 16/70] unit test for percent --- t/test_units.t | 2 ++ 1 file changed, 2 insertions(+) diff --git a/t/test_units.t b/t/test_units.t index a8760fae22..76d6d0ecce 100755 --- a/t/test_units.t +++ b/t/test_units.t @@ -67,6 +67,8 @@ is_deeply( {evaluate_units('c*yr')}, {evaluate_units('light-year')}, 'light year is_deeply( multiply_by((180/$Units::PI)**2, evaluate_units('deg^2')), {evaluate_units('sr')}, 'solid angle conversion'); +is_deeply( multiply_by(0.01), {evaluate_units('%')}, 'percent conversion'); + } ok( units_well_defined(), "checking unit definitions: $error_message"); From 131bed99a79073d4386c75806c3ac35cfe016f0a Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Sat, 30 Sep 2017 20:53:10 -0700 Subject: [PATCH 17/70] add 'sec', 'second', and 'seconds' to units --- lib/Units.pm | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/Units.pm b/lib/Units.pm index 736332da49..f994ba2232 100644 --- a/lib/Units.pm +++ b/lib/Units.pm @@ -121,7 +121,7 @@ our %known_units = ('m' => { 'factor' => 1, 'rad' => 1 }, - 'sr' => { + 'sr' => { 'factor' => 1, 'rad' => 2 }, @@ -134,6 +134,18 @@ our %known_units = ('m' => { # yr -- years -- 365 days in a year # fortnight -- (FFF system) 2 weeks # + 'sec' => { + 'factor' => 1, + 's' => 1 + }, + 'second' => { + 'factor' => 1, + 's' => 1 + }, + 'seconds' => { + 'factor' => 1, + 's' => 1 + }, 'ms' => { 'factor' => 0.001, 's' => 1 From b65a70a5d7b7b07e6ff71a0a7eb286534090d17e Mon Sep 17 00:00:00 2001 From: Florian Heiderich Date: Sun, 1 Oct 2017 17:17:13 +0200 Subject: [PATCH 18/70] add 'dir="auto"' to elements This assures that the direction is automatically detected. In a global RTL setting entering ASCII characters should lead to a LTR direction. If entering characters in languages that are traditionally written in the RTL direction, the direction should be RTL. See also: https://www.w3schools.com/tags/att_global_dir.asp An alternative setting would be "ltr" for the moment, since most input will be left to right. But this would probably lead to problem in case of problems that require the student to write down a proof or some other form of "free text" in languages that are written from right to left. So I propose the "auto" setting. --- macros/PGbasicmacros.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/macros/PGbasicmacros.pl b/macros/PGbasicmacros.pl index b112cd3748..05993e915d 100644 --- a/macros/PGbasicmacros.pl +++ b/macros/PGbasicmacros.pl @@ -402,7 +402,7 @@ sub NAMED_ANS_RULE { # Note: codeshard is used in the css to identify input elements # that come from pg - HTML => qq!\n!. + HTML => qq!\n!. $add_html. # added for dragmath qq!\n!, @@ -488,7 +488,7 @@ sub NAMED_ANS_RULE_EXTENSION { MODES( TeX => "\\mbox{\\parbox[t]{${tcol}ex}{\\hrulefill}}", Latex2HTML => qq!\\begin{rawhtml}\n\n\\end{rawhtml}\n!, - HTML => qq!!. + HTML => qq!!. qq!! ); } From e55140e707fc8f9c10f4393d5467bf6bc6ad7e69 Mon Sep 17 00:00:00 2001 From: Michael Gage Date: Sat, 7 Oct 2017 19:29:45 -0400 Subject: [PATCH 19/70] Clean up and augment pod documentation on Matrix related files. --- lib/Matrix.pm | 73 +++- lib/Value/Matrix.pm | 109 ++++- macros/MatrixCheckers.pl | 30 +- macros/MatrixReduce.pl | 144 ++++--- macros/PGmatrixmacros.pl | 379 +++++++++-------- macros/PGmorematrixmacros.pl | 83 +++- macros/tableau.pl | 535 ++++++++++++++++++++++++ t/matrix_tableau_tests/matrix_test1.pg | 111 +++++ t/matrix_tableau_tests/tableau_test1.pg | 104 +++++ 9 files changed, 1275 insertions(+), 293 deletions(-) create mode 100755 macros/tableau.pl create mode 100644 t/matrix_tableau_tests/matrix_test1.pg create mode 100644 t/matrix_tableau_tests/tableau_test1.pg diff --git a/lib/Matrix.pm b/lib/Matrix.pm index 18c8093ff9..b2f83ce052 100644 --- a/lib/Matrix.pm +++ b/lib/Matrix.pm @@ -3,6 +3,12 @@ Matrix - Matrix of Reals Implements overrides for MatrixReal.pm for WeBWorK +In general it is better to use MathObjects Matrices (Value::Matrix) +in writing PG problem. The answer checking is much superior with better +error messages for syntax errors in student entries. Some of the +subroutines in this file are still used behind the scenes +by Value::Matrix to perform calculations, +such as decompose_LR(). =head1 DESCRIPTION @@ -68,6 +74,17 @@ sub _stringify { return($s); } +=head3 Accessor functions + + (these are deprecated for direct use. Use the covering Methods + provided by MathObject Matrices instead.) + + L($matrix) - return matrix L of the LR decomposition + R($matrix) - return matrix R of the LR decomposition + PL($matrix) return + PR($matrix + +Original matrix is P_L * L * R *P_R # obtain the Left Right matrices of the decomposition and the two pivot permutation matrices # the original is M = PL*L*R*PR sub L { @@ -119,10 +136,14 @@ sub PR { # use this permuation on the right PL*L*R*PR =M } # obtain the Left Right matrices of the decomposition and the two pivot permutation matrices # the original is M = PL*L*R*PR + + =head4 Method $matrix->rh_options +Meant for internal use when dealing with MatrixReal1 + =cut sub rh_options { @@ -137,9 +158,13 @@ sub rh_options { Method $matrix->trace Returns: scalar which is the trace of the matrix. + + Used by MathObject Matrices for calculating the trace. + Deprecated for direct use in PG questions. =cut + sub trace { my $self = shift; my $rows = $self->[1]; @@ -152,9 +177,12 @@ sub trace { $sum; } + =head4 - Method $matrix->new_from_array_ref + Method $new_matrix = $matrix->new_from_array_ref ([[a,b,c],[d,e,f]]) + + Deprecated in favor of using creation tools for MathObject Matrices =cut @@ -172,6 +200,8 @@ sub new_from_array_ref { # this will build a matrix or a row vector from [a, b Method $matrix->array_ref +Converts Matrix from an ARRAY to an ARRAY reference. + =cut sub array_ref { @@ -183,6 +213,8 @@ sub array_ref { Method $matrix->list +Converts a Matrix column vector to an ARRAY (list). + =cut sub list { # this is used only for column vectors @@ -196,29 +228,14 @@ sub list { # this is used only for column vectors @list; } -=head4 - - Method $matrix->new_from_list - -=cut - -sub new_from_list { # this builds a row vector from an array - my $class = shift; - my @list = @_; - my $cols = @list; - my $rows = 1; - my $matrix = new Matrix($rows, $cols); - my $i=1; - while(@list) { - my $elem = shift(@list); - $matrix->assign($i++,1, $elem); - } - $matrix; -} =head4 Method $matrix->new_row_matrix + + Deprecated -- there are better tools for MathObject Matrices. + +Create a row 1 by n matrix from a list. This subroutine appears to be broken =cut @@ -239,7 +256,9 @@ sub new_row_matrix { # this builds a row vector from an array =head4 Method $matrix->proj - + Provides behind the scenes calculations for MathObject Matrix->proj + Deprecated for direct use in favor of methods of MathObject matrix + =cut sub proj{ @@ -251,6 +270,8 @@ sub proj{ =head4 Method $matrix->proj_coeff + Provides behind the scenes calculations for MathObject Matrix->proj_coeff + Deprecated for direct use in favor of methods of MathObject matrix =cut @@ -271,6 +292,8 @@ sub proj_coeff{ Method $matrix->new_column_matrix + Create column matrix from an ARRAY reference (list reference) + =cut sub new_column_matrix { @@ -293,6 +316,8 @@ sub new_column_matrix { vectors. Method $matrix->new_from_col_vecs + + Deprecated: The tools for creating MathObjects Matrices are simpler =cut @@ -343,8 +368,8 @@ sub new_from_col_vecs =head4 - Method $matrix->new_from_col_vecs - + Function: cp() + Provides ability to use complex numbers. =cut sub cp { # MEG makes new copies of complex number @@ -462,6 +487,8 @@ sub transpose Method $matrix->decompose_LR + Used by MathObjects Matrix for LR decomposition + Deprecated for direct use in PG problems. =cut sub decompose_LR diff --git a/lib/Value/Matrix.pm b/lib/Value/Matrix.pm index f230931256..f7c4df3368 100644 --- a/lib/Value/Matrix.pm +++ b/lib/Value/Matrix.pm @@ -3,6 +3,109 @@ # Implements the Matrix class. # # @@@ Still needs lots of work @@@ + +=head1 Value::Matrix class + + +References: + +MathObject Matrix methods: L +MathObject Contexts: L +CPAN RealMatrix docs: L + +Allowing Matrices in Fractions: +L + + Context()->parens->set("[" => {formMatrix => 1}); + +Files interacting with Matrices: + +L L +L + +L -- checking whether vectors form a basis +L -- tools for row reduction via elementary matrices +L -- Generates unimodular matrices with real entries +L +L +L +L +L +L +Contexts + + Matrix -- allows students to enter [[3,4],[3,6]] + -- formMatrix =>1 also allows this? + Complex-Matrix -- allows complex entries + +Creation methods + + $M1 = Matrix([1,2],[3,4]); + $M2 = Matrix([5,6],[7,8]); + $v = Vector(9,10); + $w = ColumnVector(9,10); # differs in how it is printed + +Commands added in Value::matrix + + Conversion + $matrix->values produces [[3,4,5],[1,3,4]] recursive array references of numbers (not MathObjects) + $matrix->wwMatrix produces CPAN MatrixReal1 matrix, used for computation subroutines + + Information + $matrix->dimension: ARRAY + + Access values + + row : MathObjectMatrix + column : MathObjectMatrix + element : Real or Complex value + + Assign values + + these need to be added: + +see C in MatrixReduce and L + + Advanced + $matrix->data: ARRAY reference (internal data) of MathObjects (Real,Complex, Fractions) + stored at each location. + + +Passthrough methods covering subroutines in Matrix.pm which overrides or +augment CPAN's MatrixReal1.pm. Matrix is a specialized subclass of MatrixReal1.pm + +The actual calculations are done in Matrix.pm + + trace + proj + proj_coeff + L + R + PL + PR + +Passthrough methods covering subroutines in MatrixReal1.pm +(this has been modified to handle complex numbers) +The actual calculations are done in MatrixReal1.pm subroutines + + condition + det + inverse + is_symmetric + decompose_LR + dim + norm_one + norm_max + kleene + normalize + solve_LR (also solve()) + order_LR (also order() + solve_GSM + solve_SSM + solve_RM + +=cut + # package Value::Matrix; my $pkg = 'Value::Matrix'; @@ -17,7 +120,7 @@ our @ISA = qw(Value); # a point, vector or matrix object, a matrix-valued formula, or a string # that evaluates to a matrix # -sub new { +sub new { #internal my $self = shift; my $class = ref($self) || $self; my $context = (Value::isContext($_[0]) ? shift : $self->context); my $M = shift; $M = [] unless defined $M; $M = [$M,@_] if scalar(@_) > 0; @@ -38,7 +141,7 @@ sub new { # (Recursively) make a matrix from a list of array refs # and report errors about the entry types # -sub matrixMatrix { +sub matrixMatrix { #internal my $self = shift; my $class = ref($self) || $self; my $context = shift; my ($x,$m); my @M = (); my $isFormula = 0; @@ -62,7 +165,7 @@ sub matrixMatrix { # Form a 1 x n matrix from a list of numbers # (could become a row of an m x n matrix) # -sub numberMatrix { +sub numberMatrix { #internal my $self = shift; my $class = ref($self) || $self; my $context = shift; my @M = (); my $isFormula = 0; diff --git a/macros/MatrixCheckers.pl b/macros/MatrixCheckers.pl index 6f4c963173..ec13808365 100644 --- a/macros/MatrixCheckers.pl +++ b/macros/MatrixCheckers.pl @@ -87,23 +87,23 @@ =head1 DESCRIPTION are produced by C<\(\Bigg\lbrace\)> and C<\(\Bigg\rbrace\)>, are a matter of personal preference (since a basis is an ordered set, I like to include braces). -=over 12 -Context()->texStrings; -BEGIN_TEXT -Find an orthonormal basis for... -$BR -$BR -$BCENTER -\(\Bigg\lbrace\) -\{ $multians->ans_array(15) \}, -\{ $multians->ans_array(15) \} -\(\Bigg\rbrace.\) -$ECENTER -END_TEXT -Context()->normalStrings; -=back + Context()->texStrings; + BEGIN_TEXT + Find an orthonormal basis for... + $BR + $BR + $BCENTER + \(\Bigg\lbrace\) + \{ $multians->ans_array(15) \}, + \{ $multians->ans_array(15) \} + \(\Bigg\rbrace.\) + $ECENTER + END_TEXT + Context()->normalStrings; + + The answer evaluation section of the PG file is totally standard. diff --git a/macros/MatrixReduce.pl b/macros/MatrixReduce.pl index 010d732269..a77925dc76 100644 --- a/macros/MatrixReduce.pl +++ b/macros/MatrixReduce.pl @@ -14,27 +14,45 @@ =head1 SYNOPSIS =over 12 -=item Get the reduced row echelon form: C<$Areduced = rref($A);> Should be used in the fraction context with all entries of $A made into fractions. +=item Get the reduced row echelon form: C<$Areduced = rref($A);> + +Should be used in the fraction context with all entries of $A made into fractions. -=item Make matrix entries do fraction arithmetic (rather than decimal arithmetic): After selecting the Fraction context using Context('Fraction')->parens->set("[" => {formMatrix => 1}), C<$A = apply_fraction_to_matrix_entries($A);> applies Fraction() to all of the entries of $A, which makes subsequent matrix algebra computations with $A use fraction arithmetic. +=item Make matrix entries do fraction arithmetic (rather than decimal arithmetic): + +After selecting the Fraction context using Context('Fraction')->parens->set("[" => {formMatrix => 1}), C<$A = apply_fraction_to_matrix_entries($A);> applies Fraction() to all of the entries of $A, which makes subsequent matrix algebra computations with $A use fraction arithmetic. =item Get the reduced column echelon form: C<$Areduced = rcef($A);> -=item Change the value of a matrix entry: C changes the [2,3] entry to the value 50. +=item Change the value of a matrix entry: C + +changes the [2,3] entry to the value 50. =item Construct an n x n identity matrix: C<$E = identity_matrix(5);> -=item Construct an n x n elementary matrix that will permute rows i and j: C<$E = elem_matrix_row_switch(5,2,4);> creates a 5 x 5 identity matrix and swaps rows 2 and 4. +=item Construct an n x n elementary matrix that will permute rows i and j: + +C<$E = elem_matrix_row_switch(5,2,4);> creates a 5 x 5 identity matrix and swaps rows 2 and 4. + +=item Construct an n x n elementary matrix that will multiply row i by s: C<$E = elem_matrix_row_mult(5,2,4);> + +creates a 5 x 5 identity matrix and swaps puts 4 in the second spot on the diagonal. -=item Construct an n x n elementary matrix that will multiply row i by s: C<$E = elem_matrix_row_mult(5,2,4);> creates a 5 x 5 identity matrix and swaps puts 4 in the second spot on the diagonal. +=item Construct an n x n elementary matrix that will multiply row i by s: C<$E3 = elem_matrix_row_add(5,3,1,35);> -=item Construct an n x n elementary matrix that will multiply row i by s: C<$E3 = elem_matrix_row_add(5,3,1,35);> creates a 5 x 5 identity matrix and swaps puts 35 in the (3,1) position. +creates a 5 x 5 identity matrix and swaps puts 35 in the (3,1) position. -=item Perform the row switch transform that swaps (row i) with (row j): C<$Areduced = row_switch($A,2,4);> swaps rows 2 and 4 in matrix $A. +=item Perform the row switch transform that swaps (row i) with (row j): C<$Areduced = row_switch($A,2,4);> -=item Perform the row multiplication transform s * (row i) placed into (row i): C<$Areduced = row_mult(A,2,10);> multiplies every entry in row 2 of $A by 10. +swaps rows 2 and 4 in matrix $A. -=item Perform the row addition transform (row i) + s * (row j) placed into (row i): C<$Areduced = row_add($A,2,1,10);> adds 10 times row 1 to row 2 and places the result in row 2. (Same as constructing $E to be the identity with 10 placed in entry (2,1), then multiplying $E * $A.) +=item Perform the row multiplication transform s * (row i) placed into (row i): C<$Areduced = row_mult(A,2,10);> + +multiplies every entry in row 2 of $A by 10. + +=item Perform the row addition transform (row i) + s * (row j) placed into (row i): C<$Areduced = row_add($A,2,1,10);> + +adds 10 times row 1 to row 2 and places the result in row 2. (Same as constructing $E to be the identity with 10 placed in entry (2,1), then multiplying $E * $A.) =back @@ -42,61 +60,59 @@ =head1 DESCRIPTION Usage: -=over 12 - -DOCUMENT(); -loadMacros( -"PGstandard.pl", -"MathObjects.pl", -"MatrixReduce.pl", # automatically loads contextFraction.pl and MathObjects.pl -"PGcourse.pl", -); -$showPartialCorrectAnswers = 0; -TEXT(beginproblem()); - -# Context('Matrix'); # for decimal arithmetic -Context('Fraction'); # for fraction arithmetic - -$A = Matrix([ -[random(-5,5,1),random(-5,5,1),random(-5,5,1),3], -[random(-5,5,1),random(-5,5,1),random(-5,5,1),0.75], -[random(-5,5,1),random(-5,5,1),random(-5,5,1),9/4], -]); - -$A = apply_fraction_to_matrix_entries($A); # try commenting this line out for different results - -$Arref = rref($A); - -$Aswitch = row_switch($A, 2, 3); - -$Amult = row_mult($A, 2, 4); - -$Aadd = row_add($A, 2, 1, 10); - -$E = elem_matrix_row_add(3,2,1,10); -$EA = $E * $A; - -$E1 = elem_matrix_row_switch(5,2,4); -$E2 = elem_matrix_row_mult(5,4,Fraction(1/10)); -$E3 = elem_matrix_row_add(5,3,1,35); -$E4 = identity_matrix(4); -change_matrix_entry($E4,[3,2],10); - -Context()->texStrings; -BEGIN_TEXT -The original matrix and its row reduced echelon form: -\[ $A \sim $Arref. \] -$BR -The original matrix with rows switched, multiplied, or added together: -\[ $Aswitch, $Amult, $Aadd. \] -$BR -Some elementary matrices. -\[$E1, $E2, $E3, $E4\] -END_TEXT -Context()->normalStrings; - -COMMENT('MathObject version.'); -ENDDOCUMENT(); + DOCUMENT(); + loadMacros( + "PGstandard.pl", + "MathObjects.pl", + "MatrixReduce.pl", # automatically loads contextFraction.pl and MathObjects.pl + "PGcourse.pl", + ); + $showPartialCorrectAnswers = 0; + TEXT(beginproblem()); + + # Context('Matrix'); # for decimal arithmetic + Context('Fraction'); # for fraction arithmetic + + $A = Matrix([ + [random(-5,5,1),random(-5,5,1),random(-5,5,1),3], + [random(-5,5,1),random(-5,5,1),random(-5,5,1),0.75], + [random(-5,5,1),random(-5,5,1),random(-5,5,1),9/4], + ]); + + $A = apply_fraction_to_matrix_entries($A); # try commenting this line out for different results + + $Arref = rref($A); + + $Aswitch = row_switch($A, 2, 3); + + $Amult = row_mult($A, 2, 4); + + $Aadd = row_add($A, 2, 1, 10); + + $E = elem_matrix_row_add(3,2,1,10); + $EA = $E * $A; + + $E1 = elem_matrix_row_switch(5,2,4); + $E2 = elem_matrix_row_mult(5,4,Fraction(1/10)); + $E3 = elem_matrix_row_add(5,3,1,35); + $E4 = identity_matrix(4); + change_matrix_entry($E4,[3,2],10); + + Context()->texStrings; + BEGIN_TEXT + The original matrix and its row reduced echelon form: + \[ $A \sim $Arref. \] + $BR + The original matrix with rows switched, multiplied, or added together: + \[ $Aswitch, $Amult, $Aadd. \] + $BR + Some elementary matrices. + \[$E1, $E2, $E3, $E4\] + END_TEXT + Context()->normalStrings; + + COMMENT('MathObject version.'); + ENDDOCUMENT(); =back diff --git a/macros/PGmatrixmacros.pl b/macros/PGmatrixmacros.pl index 070646c63a..c40d183b74 100644 --- a/macros/PGmatrixmacros.pl +++ b/macros/PGmatrixmacros.pl @@ -12,11 +12,20 @@ =head1 SYNPOSIS =head1 DESCRIPTION -Almost all of the macros in the file are very rough at best. The most useful is display_matrix. -Many of the other macros work with vectors and matrices stored as anonymous arrays. +These macros are fairly old. The most useful is display_matrix and +its variants. -Frequently it may be -more useful to use the Matrix objects defined RealMatrix.pm and Matrix.pm and the constructs listed there. +Frequently it will be +most useful to use the MathObjects Matrix (defined in Value::Matrix.pm) +and Vector types which +have more capabilities and more error checking than the subroutines in +this file. These macros have no object orientation and +work with vectors and matrices +stored as perl anonymous arrays. + +There are also Matrix objects defined in +RealMatrix.pm and Matrix.pm but in almost all cases the +MathObjects Matrix types are preferable. =cut @@ -28,132 +37,57 @@ BEGIN sub _PGmatrixmacros_init { } -# this subroutine zero_check is not very well designed below -- if it is used much it should receive -# more work -- particularly for checking relative tolerance. More work needs to be done if this is -# actually used. - -sub zero_check{ - my $array = shift; - my %options = @_; - my $num = @$array; - my $i; - my $max = 0; my $mm; - for ($i=0; $i< $num; $i++) { - $mm = $array->[$i] ; - $max = abs($mm) if abs($mm) > $max; - } - my $tol = $options{tol}; - $tol = 0.01*$options{reltol}*$options{avg} if defined($options{reltol}) and defined $options{avg}; - $tol = .000001 unless defined($tol); - ($max <$tol) ? 1: 0; # 1 if the array is close to zero; -} -sub vec_dot{ - my $vec1 = shift; - my $vec2 = shift; - warn "vectors must have the same length" unless @$vec1 == @$vec2; # the vectors must have the same length. - my @vec1=@$vec1; - my @vec2=@$vec2; - my $sum = 0; - - while(@vec1) { - $sum += shift(@vec1)*shift(@vec2); - } - $sum; -} -sub proj_vec { - my $vec = shift; - warn "First input must be a column matrix" unless ref($vec) eq 'Matrix' and ${$vec->dim()}[1] == 1; - my $matrix = shift; # the matrix represents a set of vectors spanning the linear space - # onto which we want to project the vector. - warn "Second input must be a matrix" unless ref($matrix) eq 'Matrix' and ${$matrix->dim()}[1] == ${$vec->dim()}[0]; - $matrix * transpose($matrix) * $vec; -} - -sub vec_cmp{ #check to see that the submitted vector is a non-zero multiple of the correct vector - my $correct_vector = shift; - my %options = @_; - my $ans_eval = sub { - my $in = shift @_; - - my $ans_hash = new AnswerHash; - my @in = split("\0",$in); - my @correct_vector=@$correct_vector; - $ans_hash->{student_ans} = "( " . join(", ", @in ) . " )"; - $ans_hash->{correct_ans} = "( " . join(", ", @correct_vector ) . " )"; - - return($ans_hash) unless @$correct_vector == @in; # make sure the vectors are the same dimension - - my $correct_length = vec_dot($correct_vector,$correct_vector); - my $in_length = vec_dot(\@in,\@in); - return($ans_hash) if $in_length == 0; - - if (defined($correct_length) and $correct_length != 0) { - my $constant = vec_dot($correct_vector,\@in)/$correct_length; - my @difference = (); - for(my $i=0; $i < @correct_vector; $i++ ) { - $difference[$i]=$constant*$correct_vector[$i] - $in[$i]; - } - $ans_hash->{score} = zero_check(\@difference); - - } else { - $ans_hash->{score} = 1 if vec_dot(\@in,\@in) == 0; - } - $ans_hash; - - }; - - $ans_eval; -} ############ =head4 display_matrix - Usage \{ display_matrix( [ [1, '\(\sin x\)'], [ans_rule(5), 6] ]) \} - \{ display_matrix($A, align=>'crvl') \} - \[ \{ display_matrix_mm($A) \} \] - \[ \{ display_matrix_mm([ [1, 3], [4, 6] ]) \} \] - - display_matrix produces a matrix for display purposes. It checks whether - it is producing LaTeX output, or if it is displaying on a web page in one - of the various modes. The input can either be of type Matrix, Value::Matrix (mathobject) - or a reference to an array. - - Entries can be numbers, Fraction objects, bits of math mode, or answer - boxes. An entire row can be replaced by the string 'hline' to produce - a horizontal line in the matrix. - - display_matrix_mm functions similarly, except that it should be inside - math mode. display_matrix_mm cannot contain answer boxes in its entries. - Entries to display_matrix_mm should assume that they are already in - math mode. - - Both functions take an optional alignment string, similar to ones in - LaTeX tabulars and arrays. Here c for centered columns, l for left - flushed columns, and r for right flushed columns. - - The alignment string can also specify vertical rules to be placed in the - matrix. Here s or | denote a solid line, d is a dashed line, and v - requests the default vertical line. This can be set on a system-wide - or course-wide basis via the variable $defaultDisplayMatrixStyle, and - it can default to solid, dashed, or no vertical line (n for none). - - The matrix has left and right delimiters also specified by - $defaultDisplayMatrixStyle. They can be parentheses, square brackets, - braces, vertical bars, or none. The default can be overridden in - an individual problem with optional arguments such as left=>"|", or - right=>"]". - - You can specify an optional argument of 'top_labels'=> ['a', 'b', 'c']. - These are placed above the columns of the matrix (as is typical for - linear programming tableau, for example). The entries will be typeset - in math mode. - - Top labels require a bit of care. For image modes, they look better - with display_matrix_mm where it is all one big image, but they work with - display_matrix. With tth, you pretty much have to use display_matrix - since tth can't handle the TeX tricks used to get the column headers - up there if it gets the whole matrix at once. + Usage + \{ display_matrix( [ [1, '\(\sin x\)'], [ans_rule(5), 6] ]) \} + \{ display_matrix($A, align=>'crvl') \} + \[ \{ display_matrix_mm($A) \} \] + \[ \{ display_matrix_mm([ [1, 3], [4, 6] ]) \} \] + +display_matrix produces a matrix for display purposes. It checks whether +it is producing LaTeX output, or if it is displaying on a web page in one +of the various modes. The input can either be of type Matrix, Value::Matrix (mathobject) +or a reference to an array. + +Entries can be numbers, Fraction objects, bits of math mode, or answer +boxes. An entire row can be replaced by the string 'hline' to produce +a horizontal line in the matrix. + +display_matrix_mm functions similarly, except that it should be inside +math mode. display_matrix_mm cannot contain answer boxes in its entries. +Entries to display_matrix_mm should assume that they are already in +math mode. + +Both functions take an optional alignment string, similar to ones in +LaTeX tabulars and arrays. Here c for centered columns, l for left +flushed columns, and r for right flushed columns. + +The alignment string can also specify vertical rules to be placed in the +matrix. Here s or | denote a solid line, d is a dashed line, and v +requests the default vertical line. This can be set on a system-wide +or course-wide basis via the variable $defaultDisplayMatrixStyle, and +it can default to solid, dashed, or no vertical line (n for none). + +The matrix has left and right delimiters also specified by +$defaultDisplayMatrixStyle. They can be parentheses, square brackets, +braces, vertical bars, or none. The default can be overridden in +an individual problem with optional arguments such as left=>"|", or +right=>"]". + +You can specify an optional argument of 'top_labels'=> ['a', 'b', 'c']. +These are placed above the columns of the matrix (as is typical for +linear programming tableau, for example). The entries will be typeset +in math mode. + +Top labels require a bit of care. For image modes, they look better +with display_matrix_mm where it is all one big image, but they work with +display_matrix. With tth, you pretty much have to use display_matrix +since tth can't handle the TeX tricks used to get the column headers +up there if it gets the whole matrix at once. =cut @@ -692,6 +626,7 @@ sub mbox { =head4 ra_flatten_matrix Usage: ra_flatten_matrix($A) + returns: [a11, a12,a21,a22] where $A is a matrix object The output is a reference to an array. The matrix is placed in the array by iterating @@ -715,9 +650,16 @@ sub ra_flatten_matrix{ \@array; } -# This subroutine is probably obsolete and not generally useful. It was patterned after the APL -# constructs for multiplying matrices. It might come in handy for non-standard multiplication of -# of matrices (e.g. mod 2) for indice matrices. += head4 apl_matrix_mult() + + # This subroutine is probably obsolete and not generally useful. + # It was patterned after the APL + # constructs for multiplying matrices. It might come in handy + # for non-standard multiplication of + # of matrices (e.g. mod 2) for indice matrices. + +=cut + sub apl_matrix_mult{ my $ra_a= shift; my $ra_b= shift; @@ -763,9 +705,11 @@ sub make_matrix{ =head4 create2d_matrix -This can be a useful method for quickly entering small matrices by hand. --MEG +This can be a useful method for quickly entering small matrices by hand. + --MEG - create2d_matrix("1 2 4, 5 6 8"); + create2d_matrix("1 2 4, 5 6 8"); or + create2d_matrix("1 2 4; 5 6 8"); produces the anonymous array [[1,2,4],[5,6,8] ] @@ -775,11 +719,42 @@ =head4 create2d_matrix sub create2d_matrix { my $string = shift; - my @rows = split("\\s*,\\s*",$string); + my @rows = split("\\s*[,;]\\s*",$string); @rows = map {[split("\\s", $_ )]} @rows; [@rows]; } + +=head2 convert_to_array_ref { + + $output_matrix = convert_to_array_ref($input_matrix) + +Converts a MathObject matrix (ref($input_matrix eq 'Value::Matrix') +or a MatrixReal1 matrix (ref($input_matrix eq 'Matrix')to +a reference to an array (e.g [[4,6],[3,2]]). +This adaptor allows all of the LinearProgramming.pl subroutines to be used with +MathObject arrays. + +$mathobject_matrix->value outputs an array (usually an array of array references) so placing it inside +square bracket produces and array reference (of array references) which is what lp_display_mm() is +seeking. + +=cut + +sub convert_to_array_ref { + my $input = shift; + if (ref($input) eq 'Value::Matrix' ) { + $input = [$input->value]; + } elsif (ref($input) eq 'Matrix' ) { + $input = $input->array_ref; + } elsif (ref($input) =~/ARRAY/) { + # no change to input value + } else { + WARN_MESSAGE("This does not appear to be a matrix "); + } + $input; +} + =head4 check_matrix_from_ans_box_cmp An answer checker factory built on create2d_matrix. This still needs @@ -796,7 +771,6 @@ sub check_matrix_from_ans_box_cmp{ my $string_matrix_cmp = sub { $string = shift @_; my $studentMatrix; - # eval { $studentMatrix = Matrix(create2d_matrix($string)); die "I give up";}; #caught by op_mask $studentMatrix = Matrix(create2d_matrix($string)); die "I give up"; # main::DEBUG_MESSAGE(ref($studentMatrix). "$studentMatrix with error "); # errors are returned as warnings. Can't seem to trap them. @@ -815,67 +789,100 @@ sub check_matrix_from_ans_box_cmp{ } -=head2 convert_to_array_ref { - $output_matrix = convert_to_array_ref($input_matrix) +=head4 zero_check (deprecated -- use MathObjects matrices and vectors) -Converts a MathObject matrix (ref($input_matrix eq 'Value::Matrix') -or a MatrixReal1 matrix (ref($input_matrix eq 'Matrix')to -a reference to an array (e.g [[4,6],[3,2]]). -This adaptor allows all of the Linear Programming subroutines to be used with -MathObject arrays. + # this subroutine zero_check is not very well designed below -- if it is used much it should receive + # more work -- particularly for checking relative tolerance. More work needs to be done if this is + # actually used. -$mathobject_matrix->value outputs an array (usually an array of array references) so placing it inside -square bracket produces and array reference (of array references) which is what lp_display_mm() is -seeking. +=cut + +sub zero_check{ + my $array = shift; + my %options = @_; + my $num = @$array; + my $i; + my $max = 0; my $mm; + for ($i=0; $i< $num; $i++) { + $mm = $array->[$i] ; + $max = abs($mm) if abs($mm) > $max; + } + my $tol = $options{tol}; + $tol = 0.01*$options{reltol}*$options{avg} if defined($options{reltol}) and defined $options{avg}; + $tol = .000001 unless defined($tol); + ($max <$tol) ? 1: 0; # 1 if the array is close to zero; +} + +=head4 vec_dot() (deprecated -- use MathObjects vectors and matrices) + +sub vec_dot{ + my $vec1 = shift; + my $vec2 = shift; + warn "vectors must have the same length" unless @$vec1 == @$vec2; # the vectors must have the same length. + my @vec1=@$vec1; + my @vec2=@$vec2; + my $sum = 0; + + while(@vec1) { + $sum += shift(@vec1)*shift(@vec2); + } + $sum; +} + +=head4 proj_vect (deprecated -- use MathObjects vectors and matrices) =cut -sub convert_to_array_ref { - my $input = shift; - if (ref($input) eq 'Value::Matrix' ) { - $input = [$input->value]; - } elsif (ref($input) eq 'Matrix' ) { - $input = $input->array_ref; - } elsif (ref($input) =~/ARRAY/) { - # no change to input value - } else { - WARN_MESSAGE("This does not appear to be a matrix "); - } - $input; +sub proj_vec { + my $vec = shift; + warn "First input must be a column matrix" unless ref($vec) eq 'Matrix' and ${$vec->dim()}[1] == 1; + my $matrix = shift; # the matrix represents a set of vectors spanning the linear space + # onto which we want to project the vector. + warn "Second input must be a matrix" unless ref($matrix) eq 'Matrix' and ${$matrix->dim()}[1] == ${$vec->dim()}[0]; + $matrix * transpose($matrix) * $vec; +} + +=head4 vec_cmp (deprecated -- use MathObjects vectors and matrices) + +=cut + + +sub vec_cmp{ #check to see that the submitted vector is a non-zero multiple of the correct vector + my $correct_vector = shift; + my %options = @_; + my $ans_eval = sub { + my $in = shift @_; + + my $ans_hash = new AnswerHash; + my @in = split("\0",$in); + my @correct_vector=@$correct_vector; + $ans_hash->{student_ans} = "( " . join(", ", @in ) . " )"; + $ans_hash->{correct_ans} = "( " . join(", ", @correct_vector ) . " )"; + + return($ans_hash) unless @$correct_vector == @in; # make sure the vectors are the same dimension + + my $correct_length = vec_dot($correct_vector,$correct_vector); + my $in_length = vec_dot(\@in,\@in); + return($ans_hash) if $in_length == 0; + + if (defined($correct_length) and $correct_length != 0) { + my $constant = vec_dot($correct_vector,\@in)/$correct_length; + my @difference = (); + for(my $i=0; $i < @correct_vector; $i++ ) { + $difference[$i]=$constant*$correct_vector[$i] - $in[$i]; + } + $ans_hash->{score} = zero_check(\@difference); + + } else { + $ans_hash->{score} = 1 if vec_dot(\@in,\@in) == 0; + } + $ans_hash; + + }; + + $ans_eval; } -# sub format_answer{ -# my $ra_eigenvalues = shift; -# my $ra_eigenvectors = shift; -# my $functionName = shift; -# my @eigenvalues=@$ra_eigenvalues; -# my $size= @eigenvalues; -# my $ra_eigen = make_matrix( sub {my ($i,$j) = @_; ($i==$j) ? "e^{$eigenvalues[$j] t}": 0 }, $size,$size); -# my $out = qq! -# $functionName(t) =! . -# displayMatrix(apl_matrix_mult($ra_eigenvectors,$ra_eigen, -# 'times'=>sub{($_[0] and $_[1]) ? "$_[0]$_[1]" : ''}, -# 'plus'=>sub{ my $out = join("",@_); ($out) ?$out : '0' } -# ) ) ; -# $out; -# } -# sub format_vector_answer{ -# my $ra_eigenvalues = shift; -# my $ra_eigenvectors = shift; -# my $functionName = shift; -# my @eigenvalues=@$ra_eigenvalues; -# my $size= @eigenvalues; -# my $ra_eigen = make_matrix( sub {my ($i,$j) = @_; ($i==$j) ? "e^{$eigenvalues[$j] t}": 0 }, $size,$size); -# my $out = qq! -# $functionName(t) =! . -# displayMatrix($ra_eigenvectors)."e^{$eigenvalues[0] t}" ; -# $out; -# } -# sub format_question{ -# my $ra_matrix = shift; -# my $out = qq! y'(t) = ! . displayMatrix($B). q! y(t)! -# -# } 1; diff --git a/macros/PGmorematrixmacros.pl b/macros/PGmorematrixmacros.pl index b2af761786..8ed22573a9 100644 --- a/macros/PGmorematrixmacros.pl +++ b/macros/PGmorematrixmacros.pl @@ -5,9 +5,24 @@ BEGIN # set the prefix used for arrays. our $ArRaY = $main::PG->{ARRAY_PREFIX}; +=head2 NAME + + macros/PGmorematrixmacros.pl + +=cut + + sub _PGmorematrixmacros_init{} -sub random_inv_matrix { ## Builds and returns a random invertible \$row by \$col matrix. +=head4 random_inv_matrix + +## Builds and returns a random invertible \$row by \$col matrix. + +=cut + + +sub random_inv_matrix { +## Builds and returns a random invertible \$row by \$col matrix. warn "Usage: \$new_matrix = random_inv_matrix(\$rows,\$cols)" if (@_ != 2); @@ -54,16 +69,29 @@ sub random_diag_matrix{ ## Builds and returns a random diagonal \$n by \$n matri return $D; } +=head4 swap_rows ($matrix, $row1, $row2) + + (deprecated use MathObject Matrix instead) + +$matrix is assumed to be a RealMatrix1 object. +It is better to use MathObject Matrices and row swap mechanisms +from MatrixReduce.pl instead. + +=cut + + sub swap_rows{ warn "Usage: \$new_matrix = swap_rows(\$matrix,\$row1,\$row2);" if (@_ != 3); my $matrix = $_[0]; my ($i,$j) = ($_[1],$_[2]); + warn "Error: Rows to be swapped must exist!" if ($i>@$matrix or $j >@$matrix); warn "Warning: Swapping the same row is pointless" - if ($i==$j); + if ($i==$j); + my $cols = @{$matrix->[0]}; my $B = new Matrix(@$matrix,$cols); foreach my $k (1..$cols){ @@ -73,6 +101,16 @@ sub swap_rows{ return $B; } +=head4 row_mult ($matrix, $scaler, $row) + + (deprecated use MathObject Matrix instead) + +$matrix is assumed to be a RealMatrix1 object. +It is better to use MathObject Matrices and row swap mechanisms +from MatrixReduce.pl instead. + +=cut + sub row_mult{ warn "Usage: \$new_matrix = row_mult(\$matrix,\$scalar,\$row);" @@ -88,6 +126,16 @@ sub row_mult{ return $B; } +sub linear_combo($matrix, $scalar, $row1, $row2) + + (deprecated use MathObject Matrix instead) + +Adds a multiple of row1 to row2. + +$matrix is assumed to be a RealMatrix1 object. +It is better to use MathObject Matrices and subroutines +from MatrixReduce.pl instead. + sub linear_combo{ warn "Usage: \$new_matrix = linear_combo(\$matrix,\$scalar,\$row1,\$row2);" @@ -106,6 +154,15 @@ sub linear_combo{ return $B; } + +=head2 + +These should be compared to similar subroutines made later in +MatrixCheckers.pl + + +=cut + =head3 basis_cmp() Compares a list of vectors by finding the change of coordinate matrix @@ -378,6 +435,8 @@ sub compare_basis { =head2 vec_list_string +(this is mostly obsolete. One should use MathObject Vectors instead. ) + This is a check_syntax type method (in fact I borrowed some of that method's code) for vector input. The student needs to enter vectors like: [1,0,0],[1,2,3],[0,9/sqrt(10),1/sqrt(10)] Each entry can contain functions and operations and the usual math constants (pi and e). @@ -503,8 +562,14 @@ sub vec_list_string{ $rh_ans; } + + + =head5 ans_array_filter + (this filter is not necessary when using MathObjects. It may someday be useful + again if the AnswerEvaluator pipeline is used to its fullest extent. ) + This filter was created to get, format, and evaluate each entry of the ans_array and ans_array_extension answer entry methods. Running this filter is necessary to get all the entries out of the answer hash. Each entry is evaluated and the resulting number is put in the display for student answer @@ -616,6 +681,20 @@ sub ans_array_filter{ } +=head3 + +The following subroutines, meant to be used with MatrixReal1 type matrices, are +deprecated. In general you should use the MathObject Matrix type and the +checking methods in MatrixCheckers.pl + + are_orthogonal_vecs($vec_ref, %opts) + is_diagonal($matrix, %opts) + are_unit_vecs($vec_ref, %opts) + display_correct_vecs($vec_ref, %opts) + vec_solution_cmp($vec,%opts) + filter: compare_vec_solution($rh_ans,%opts); + +=cut sub are_orthogonal_vecs{ my ($vec_ref , %opts) = @_; diff --git a/macros/tableau.pl b/macros/tableau.pl new file mode 100755 index 0000000000..bb4c7514d6 --- /dev/null +++ b/macros/tableau.pl @@ -0,0 +1,535 @@ +#!/usr/bin/perl -w + +# this file needs documentation and unit testing. +# where is it used? + +##### From gage_matrix_ops +# 2014_HKUST_demo/templates/setSequentialWordProblem/bill_and_steve.pg:"gage_matrix_ops.pl", + +=head1 Tableaus and matrices + + # We're going to have several types + # MathObject Matrices Value::Matrix + # tableaus form John Jones macros + # MathObject tableaus + # Containing an matrix $A coefficients for constraint + # A vertical vector $b for constants for constraints + # A horizontal vector $c for coefficients for objective function + # A vertical vector corresponding to the value $z of the objective function + # dimensions $n problem vectors, $m constraints = $m slack variables + # A basis Value::Set -- positions for columns which are independent and + # whose associated variables can be determined + # uniquely from the parameter variables. + # The non-basis (parameter) variables are set to zero. + # + # state variables (assuming parameter variables are zero or when given parameter variables) + # create the methods for updating the various containers + # create the method for printing the tableau with all its decorations + # possibly with switches to turn the decorations on and off. + + +The structure of the tableau is: + + ----------------------------------------------------------- + | | | | | + | A | S | 0 | b | + | | | | | + ---------------------------------------------- + | -c | 0 | 1 | 0 | + ---------------------------------------------- + Matrix A, the constraint matrix is n by m + Matrix S, the slack variables is m by m + Matrix b, the constraint constants is n by 1 + The next to the last column holds z or objective value + z(...x^i...) = c_i* x^i (Einstein summation convention) + + +=cut + +=head2 Package main + +=cut + +=item get_tableau_variable_values + + Parameters: ($MathObjectMatrix_tableau, $MathObjectSet_basis) + Returns: ARRAY or ARRAY_ref + +Returns the solution variables to the tableau assuming +that the parameter (non-basis) variables +have been set to zero. It returns a list in +array context and a reference to +an array in scalar context. + +=item lp_basis_pivot + + Parameters: ($old_tableau,$old_basis,$pivot) + Returns: ($new_tableau, Set($new_basis),\@statevars) + +=item linebreak_at_commas + + Parameters: () + Return: + + Useage: + $foochecker = $constraints->cmp()->withPostFilter( + linebreak_at_commas() + ); + +Replaces commas with line breaks in the latex presentations of the answer checker. +Used most often when $constraints is a LinearInequality math object. + + + +=head2 Package tableau + +=item Tableau->new(A=>Matrix, b=>Vector or Matrix, c=>Vector or Matrix) + + A => undef, # constraint matrix MathObjectMatrix + b => undef, # constraint constants Vector or MathObjectMatrix 1 by n + c => undef, # coefficients for objective function Vector or MathObjectMatrix 1 by n + obj_row => undef, # contains the negative of the coefficients of the objective function. + z => undef, # value for objective function + n => undef, # dimension of problem variables (columns in A) + m => undef, # dimension of slack variables (rows in A) + S => undef, # square m by m matrix for slack variables + basis => undef, # list describing the current basis columns corresponding to determined variables. + B => undef, # square invertible matrix corresponding to the current basis columns + M => undef, # matrix of consisting of all columns and all rows except for the objective function row + obj_col_num => undef, + # flag indicating the column (1 or n+m+1) for the objective value + constraint_labels => undef, + problem_var_labels => undef, + slack_var_labels => undef, + +=item $self->current_tableau + Parameters: () + Returns: A MathObjectMatrix_tableau + +This represents the current version of the tableau + +=item $self->objective_row + Parameters: () + Returns: + +=item $self->basis + Parameter: ARRAY or ARRAY_ref or () + Returns: MathObject_list + + FiXME -- this should accept a MathObject_List (or MO_Set?) + +=head3 Package Tableau (eventually package Matrix?) + +=item $self->row_slice + + Parameter: @slice or \@slice + Return: MathObject matrix + +=item $self->extract_rows + + Parameter: @slice or \@slice + Return: two dimensional array ref + +=item extract_rows_to_list + + Parameter: @slice or \@slice + Return: MathObject List of row references + +=item $self->extract_columns + + Parameter: @slice or \@slice + Return: two dimensional array ref + +=item $self->column_slice + + Parameter: @slice or \@slice + Return: MathObject Matrix + +=item $self->extract_columns_to_list + + Parameter: @slice or \@slice + Return: MathObject List of Matrix references ? + +=item $self->submatrix + + Parameter:(rows=>\@row_slice,columns=>\@column_slice) + Return: MathObject matrix + + +=cut + + +sub _tableau_init {}; # don't reload this file +package main; + +sub matrix_column_slice{ + matrix_from_matrix_cols(@_); +} +sub matrix_from_matrix_cols { + my $M = shift; # a MathObject matrix_columns + my($n,$m) = $M->dimensions; + my @slice = @_; + if (ref($slice[0]) =~ /ARRAY/) { # handle array reference + @slice = @{$slice[0]}; + } + @slice = @slice?@slice : (1..$m); + my @columns = map {$M->column($_)->transpose->value} @slice; + #create the chosen columns as rows + # then transform to array_refs. + Matrix(@columns)->transpose; #transpose and return an n by m matrix (2 dim) +} +sub matrix_row_slice{ + matrix_from_matrix_rows(@_); +} + +sub matrix_from_matrix_rows { + my $M = shift; # a MathObject matrix_columns + my($n,$m) = $M->dimensions; + my @slice = @_; + if (ref($slice[0]) =~ /ARRAY/) { # handle array reference + @slice = @{$slice[0]}; + } + @slice = @slice? @slice : (1..$n); # the default is the whole matrix. + # DEBUG_MESSAGE("row slice in matrix from rows is @slice"); + my @rows = map {[$M->row($_)->value]} @slice; + #create the chosen columns as rows + # then transform to array_refs. + Matrix([@rows]); # insure that it is still an n by m matrix (2 dim) +} + +sub matrix_extract_submatrix { + matrix_from_submatrix(@_); +} +sub matrix_from_submatrix { + my $M=shift; + return undef unless ref($M) =~ /Value::Matrix/; + my %options = @_; + my($n,$m) = $M->dimensions; + my $row_slice = ($options{rows})?$options{rows}:[1..$m]; + my $col_slice = ($options{columns})?$options{columns}:[1..$n]; + # DEBUG_MESSAGE("ROW SLICE", join(" ", @$row_slice)); + # DEBUG_MESSAGE("COL SLICE", join(" ", @$col_slice)); + my $M1 = matrix_from_matrix_rows($M,@$row_slice); + # DEBUG_MESSAGE("M1 - matrix from rows) $M1"); + return matrix_from_matrix_cols($M1, @$col_slice); +} +sub matrix_extract_rows { + my $M =shift; + my @slice = @_; + if (ref($slice[0]) =~ /ARRAY/) { # handle array reference + @slice = @{$slice[0]}; + } elsif (@slice == 0) { # export all rows to List + @slice = ( 1..(($M->dimensions)[0]) ); + } + return map {$M->row($_)} @slice ; +} + +sub matrix_rows_to_list { + List(matrix_extract_rows(@_)); +} +sub matrix_columns_to_list { + List(matrix_extract_columns(@_) ); +} +sub matrix_extract_columns { + my $M =shift; # Add error checking + my @slice = @_; + if (ref($slice[0]) =~ /ARRAY/) { # handle array reference + @slice = @{$slice[0]}; + } elsif (@slice == 0) { # export all columns to an array + @slice = 1..($M->dimensions->[1]); + } + return map {$M->column($_)} @slice; +} + + + +######################## +############## +# get_tableau_variable_values +# +# Calculates the values of the basis variables of the tableau, +# assuming the parameter variables are 0. +# +# Usage: ARRAY = get_tableau_variable_values($MathObjectMatrix_tableau, $MathObjectSet_basis) +# +# feature request -- for tableau object -- allow specification of non-zero parameter variables +sub get_tableau_variable_values { + my $mat = shift; # a MathObject matrix + my $basis =shift; # a MathObject set + # FIXME + # type check ref($mat)='Matrix'; ref($basis)='Set'; + # or check that $mat has dimensions, element methods; and $basis has a contains method + my ($n, $m) = $mat->dimensions; + @var = (); + #DEBUG_MESSAGE( "start new matrix"); + foreach my $j (1..$m-2) { # the last two columns of the tableau are object variable and constants + if (not $basis->contains($j)) { + DEBUG_MESSAGE( "j= $j not in basis"); # set the parameter values to zero + $var[$j-1]=0; next; # non-basis variables (parameters) are set to 0. + + } else { + foreach my $i (1..$n-1) { # the last row is the objective function + # if this is a basis column there should be only one non-zero element(the pivot) + if ( $mat->element($i,$j)->value != 0 ) { # should this have ->value????? + $var[$j-1] = ($mat->element($i,$m)/($mat->element($i,$j))->value); + DEBUG_MESSAGE("i=$i j=$j var = $var[$j-1] "); # calculate basis variable value + next; + } + + } + } + } # element($n, $m-1) is the coefficient of the objective value. + # this last variable is the value of the objective function + push @var , ($mat->element($n,$m)/$mat->element($n,$m-1))->value; + + return wantarray ? @var : \@var; +} +#### Test -- assume matrix is this +# 1 2 1 0 0 | 0 | 3 +# 4 5 0 1 0 | 0 | 6 +# 7 8 0 0 1 | 0 | 9 +# -1 -2 0 0 0 | 1 | 10 # objective row +# and basis is {3,4,5} (start columns with 1) +# $n= 4; $m = 7 +# $x1=0; $x2=0; $x3=s1=3; $x4=s2=6; $x5=s3=9; w=10=objective value +# +# + +#################################### +# +# Cover for lp_pivot which allows us to use a set object for the new and old basis + +sub lp_basis_pivot { + my ($old_tableau,$old_basis,$pivot) = @_; # $pivot is a Value::Point + my $new_tableau= lp_clone($old_tableau); + # lp_pivot has 0 based indices + main::lp_pivot($new_tableau, $pivot->extract(1)-1,$pivot->extract(2)-1); + # lp_pivot pivots in place + my $new_matrix = Matrix($new_tableau); + my ($n,$m) = $new_matrix->dimensions; + my $param_size = $m-$n -1; #n=constraints+1, #m = $param_size + $constraints +2 + my $new_basis = ( $old_basis - ($pivot->extract(1)+$param_size) + ($pivot->extract(2)) )->sort; + my @statevars = get_tableau_variable_values($new_matrix, $new_basis); + return ( $new_tableau, Set($new_basis),\@statevars); + # force to set (from type Union) to insure that ->data is an array and not a string. +} + + + +sub linebreak_at_commas { + return sub { + my $ans=shift; + my $foo = $ans->{correct_ans_latex_string}; + $foo =~ s/,/,\\\\\\\\/g; + ($ans->{correct_ans_latex_string})=~ s/,/,\\\\\\\\/g; + ($ans->{preview_latex_string})=~ s/,/,\\\\\\\\/g; + #DEBUG_MESSAGE("foo", $foo); + #DEBUG_MESSAGE( "correct", $ans->{correct_ans_latex_string} ); + #DEBUG_MESSAGE( "preview", $ans->{preview_latex_string} ); + #DEBUG_MESSAGE("section4ans1 ", pretty_print($ans, $displayMode)); + $ans; + }; +} +# Useage +# $foochecker = $constraints->cmp()->withPostFilter( +# linebreak_at_commas() +# ); + + +### End gage_matrix_ops include + + + +################################################## +package Tableau; +our @ISA = qw(Value::Matrix Value); + +sub _Matrix { # can we just import this? + # this is a function, not a method + Value::Matrix->new(@_); +} + +sub new { + my $self = shift; my $class = ref($self) || $self; + my $context = (Value::isContext($_[0]) ? shift : $self->context); + my $tableau = { + A => undef, # constraint matrix MathObjectMatrix + b => undef, # constraint constants Vector or MathObjectMatrix 1 by n + c => undef, # coefficients for objective function Vector or MathObjectMatrix 1 by n + obj_row => undef, # contains the negative of the coefficients of the objective function. + z => undef, # value for objective function + n => undef, # dimension of problem variables (columns in A) + m => undef, # dimension of slack variables (rows in A) + S => undef, # square m by m matrix for slack variables + basis => undef, # list describing the current basis columns corresponding to determined variables. + B => undef, # square invertible matrix corresponding to the current basis columns + M => undef, # matrix of consisting of all columns and all rows except for the objective function row and column + obj_col_num => undef, # flag indicating the column (1 or n+m+1) for the objective value + constraint_labels => undef, + problem_var_labels => undef, + slack_var_labels => undef, + @_, + }; + bless $tableau, $class; + $tableau->initialize(); + return $tableau; +} + +# the following are used to construct the tableau +# initialize +# assemble_matrix +# objective_row +sub initialize { + $self= shift; + unless (ref($self->{A}) =~ /Value::Matrix/ && + ref($self->{b}) =~ /Value::Vector|Value::Matrix/ && + ref($self->{c}) =~ /Value::Vector|Value::Matrix/){ + main::WARN_MESSAGE("Error: Required inputs: Tableau(A=> Matrix, b=>Vector, c=>Vector)"); + return; + } + my ($m, $n)=($self->{A}->dimensions); + $self->{n}=$self->{n}//$n; + $self->{m}=$self->{m}//$m; + # main::DEBUG_MESSAGE("m $m, n $n"); + $self->{S} = Value::Matrix->I($m); + $self->{basis} = [($n+1)...($n+$m)] unless ref($self->{basis})=~/ARRAY/; + my @rows = $self->assemble_matrix; + #main::DEBUG_MESSAGE("rows", @rows); + $self->{M} = _Matrix([@rows]); + $self->{B} = $self->{M}->submatrix(rows=>[1..($self->{m})],columns=>$self->{basis}); + $self->{obj_row} = _Matrix($self->objective_row()); + return(); +} + +sub assemble_matrix { + my $self = shift; + my @rows =(); + my $m = $self->{m}; + my $n = $self->{n}; + foreach my $i (1..$m) { + my @current_row=(); + foreach my $j (1..$n) { + push @current_row, $self->{A}->element($i, $j); + } + foreach my $j (1..$m) { + push @current_row, $self->{S}->element($i,$j); # slack variables + } + push @current_row, 0, $self->{b}->data->[$i-1]; # obj column and constant column + push @rows, [@current_row]; + } + + return @rows; # these are the matrices A | S | obj | b + # the final row describing the objective function is not in this +} + +sub objective_row { + my $self = shift; + my @last_row=(); + push @last_row, ( -($self->{c}) )->value; + foreach my $i (1..($self->{m})) { push @last_row, 0 }; + push @last_row, 1, 0; + return \@last_row; +} + +# return a matrix containing the entire tableau +sub current_tableau { + my $Badj = ($self->{B}->det) * ($self->{B}->inverse); + my $current_tableau = $Badj * $self->{M}; # the A | S | obj | b + $self->{current_tableau}=$current_tableau; + # find the coefficients associated with the basis columns + my $c_B = $self->{obj_row}->extract_columns($self->{basis} ); + my $c_B2 = Value::Vector->new([ map {$_->value} @$c_B]); + my $correction_coeff = ($c_B2*$current_tableau )->row(1); + # subtract the correction coefficients from the obj_row + # this essentially extends Gauss reduction applied to the obj_row + my $obj_row_normalized = ($self->{B}->det) *$self->{obj_row}; + my $current_coeff = $obj_row_normalized-$correction_coeff ; + $self->{current_coeff}= $current_coeff; + + #main::DEBUG_MESSAGE("subtract these two ", (($self->{B}->det) *$self->{obj_row}), " | ", ($c_B*$current_tableau)->dimensions); + #main::DEBUG_MESSAGE("all coefficients", join('|', $self->{obj_row}->value ) ); + #main::DEBUG_MESSAGE("current coefficients", join('|', @current_coeff) ); + #main::DEBUG_MESSAGE("type of $self->{basis}", ref($self->{basis}) ); + #main::DEBUG_MESSAGE("current basis",join("|", @{$self->{basis}})); + #main::DEBUG_MESSAGE("CURRENT STATE ", $current_tableau); + return _Matrix( @{$current_tableau->extract_rows},$self->{current_coeff} ); + #return( $self->{current_coeff} ); +} + +sub basis { + my $self = shift; #update basis + my @input = @_; + return Value::List->new($self->{basis}) unless @input; #return basis if no input + my $new_basis; + if (ref( $input[0]) =~/ARRAY/) { + $new_basis=$input[0]; + } else { + $new_basis = \@input; + } + $self->{basis}= $new_basis; + $self->{B} = $self->{M}->submatrix(rows=>[1..($self->{m})],columns=>$self->{basis}); + return Value::List->new($self->{basis}); +} + + + + +package Value::Matrix; + +sub _Matrix { + Value::Matrix->new(@_); +} + +sub row_slice { + $self = shift; + @slice = @_; + return _Matrix( $self->extract_rows(@slice) ); +} +sub extract_rows { + $self = shift; + my @slice = @_; + if (ref($slice[0]) =~ /ARRAY/) { # handle array reference + @slice = @{$slice[0]}; + } elsif (@slice == 0) { # export all rows to List + @slice = ( 1..(($self->dimensions)[0]) ); + } + return [map {$self->row($_)} @slice ]; #prefer to pass references when possible +} +sub column_slice { + $self = shift; + return _Matrix( $self->extract_columns(@_) )->transpose; # matrix is built as rows then transposed. +} +sub extract_columns { + $self = shift; + my @slice = @_; + if (ref($slice[0]) =~ /ARRAY/) { # handle array reference + @slice = @{$slice[0]}; + } elsif (@slice == 0) { # export all columns to an array + @slice = ( 1..(($self->dimensions)[1] ) ); + } + return [map { $self->transpose->row($_) } @slice] ; + # returns the columns as an array of 1 by n row matrices containing values + # if you pull columns directly you get an array of 1 by n column vectors. + # prefer to pass references when possible +} +sub extract_rows_to_list { + my $self = shift; + Value::List->new($self->extract_rows(@_)); +} +sub extract_columns_to_list { + my $self = shift; + Value::List->new($self->extract_columns(@_) ); +} + +sub submatrix { + my $self = shift; + my %options = @_; + my($m,$n) = $self->dimensions; + my $row_slice = ($options{rows})?$options{rows}:[1..$m]; + my $col_slice = ($options{columns})?$options{columns}:[1..$n]; + return $self->row_slice($row_slice)->column_slice($col_slice); +} + + + +1; diff --git a/t/matrix_tableau_tests/matrix_test1.pg b/t/matrix_tableau_tests/matrix_test1.pg new file mode 100644 index 0000000000..c9b2c5f3bc --- /dev/null +++ b/t/matrix_tableau_tests/matrix_test1.pg @@ -0,0 +1,111 @@ +############################################## +DOCUMENT(); + +loadMacros( + "PGstandard.pl", # Standard macros for PG language + "MathObjects.pl", + "parserLinearInequality.pl", + "PGML.pl", + "tableau.pl", + "PGmatrixmacros.pl", + "LinearProgramming.pl", + #"source.pl", # allows code to be displayed on certain sites. + "PGcourse.pl", +); + +############################################## + +Context("Matrix"); # need Matrix context to allow string input into Matrix. + +#Construct a small test matrix. +$m = Matrix("[[3,6,7],[2,1,8],[4,6,21],[-6,7,9]]"); + + + $m_rows = $m->extract_rows([4,1]); #outputs an array reference + $m_cols = $m->extract_columns([3,2,1]); #outputs the columns as rows + $m1list = List($m_rows); + $m2list = List($m_cols); + $list_cols = $m->extract_columns_to_list(1,2); + $list_rows = $m->extract_rows_to_list(2,3); + + $m1matrix = Matrix($m_rows); + $m2matrix = Matrix($m_cols)->transpose; #matrix is built with rows and then needs to be transposed + + + $m3 = $m->row_slice([4,1]); + $m4 = $m->column_slice([3,2,1]); + + $submatrix1 = $m->submatrix(rows=>[2,3],columns=>[1,2]); + $submatrix2 = $m->submatrix(rows=>[2,3]); + $submatrix3 = $m->submatrix(columns=>[1,2]); + $submatrix4 = $m->submatrix(); + + +BEGIN_PGML + +Create a matrix: matrix m = [`[$m]`] + +Matrix indices start at 1. + +Extracting rows and columns from a matrix. +These are best displayed in PGML within a list. +It is best to place them in the list outside of PGML and then display. + +You can also group the extracted rows into a matrix and display. + +Matrix m = [`[$m]`] + +List rows 4,1 of the matrix m :[` [@ List($m_rows) @] `], + +or create the list before hand [`[$m1list]`] + +or matrix version [`[$m1matrix]`] + +You can do the same for columns. Notice that this does not +do exactly what you expect when you extract columns + +matrix m = [`[$m]`] + +List columns 3,2,1 of the matrix m: [@ List($m_cols) @] + +create the list before hand [`[$m2list]`] + +or create matrix version [`[$m2matrix]`] + + +Using the \[@ ... @\] escape to apply List doesn't +always do the best job +of interacting with TeX output as you can see in the first entries for List above. + +The two entries below show what happens if you extract rows or +columns into lists using +the method $m->extract_rows_to_list(). +It is the same as applying List() to the method +$m->extract_rows outside of PGML. + +matrix: [`[$m]`] + +Find the list of columns 1,2 of the matrix [`[$list_cols]`] + +Find the list of rows 2,3 of the matrix [`[$list_rows]`] + +The next two entries illustrate the $m->row_slice, $m->column_slice methods for shuffling or +selecting rows or columns from a matrix. The return type is Value::Matrix. The column +selection is done by doing row selection on the transpose -- this does what you expect +when selecting columns. + +Row slice (4,1) [`[$m3]`] + +Column slice (3,2,1) [`[$m4]`] + +This final group selects a rectangular submatrix of a matrix. ->submatrix. + +Select \[2,3\]x\[1,2\] to get [`[$submatrix1]`] of [`[$m]`] + +Select \[2,3\]x all to get [`[$submatrix2]`] + +Select all x \[1,2\] to get [`[$submatrix3]`] + +END_PGML + +ENDDOCUMENT(); \ No newline at end of file diff --git a/t/matrix_tableau_tests/tableau_test1.pg b/t/matrix_tableau_tests/tableau_test1.pg new file mode 100644 index 0000000000..cc0a503fe7 --- /dev/null +++ b/t/matrix_tableau_tests/tableau_test1.pg @@ -0,0 +1,104 @@ + + + +############################################## +DOCUMENT(); + +loadMacros( + "PGstandard.pl", # Standard macros for PG language + "MathObjects.pl", + "parserLinearInequality.pl", + "PGML.pl", + "tableau.pl", + "PGmatrixmacros.pl", + "LinearProgramming.pl", + #"source.pl", # allows code to be displayed on certain sites. + "PGcourse.pl", +); + +############################################## + +Context("Matrix"); # need Matrix context to allow string input into Matrix. + +$m = Matrix("[[3,6,7],[2,1,8],[4,6,21],[-6,7,9]]"); +$constraint_matrix = Matrix(" +[[ 0, 0, -1, -1], + [-1, -1, 0, 0 ], + [1, 0 , 1 , 0], + [0, 1, 0, 1]] +"); + +#TEXT ("created ". ref($m)); +#what are the best ways to display a matrix? + +$m1 = $m->row_slice([4,1]); +$m2 = $m->column_slice([3,2,1]); + +$list = $m->extract_rows_to_list(2,3); + +$b = Matrix([1, 2, 3, 4]); +#TEXT($BR, "vector", $b->data->[1]); +$c = Matrix([5, 6, 7]); +$t = Tableau->new(A=>$m,b=>$b, c=>$c); + +$basis2 = $t->basis(1,3,4,6); +$t->current_tableau; + +$c_B = $t->{obj_row}->extract_columns($t->{basis} ); #basis coefficients +$c_B2 = Value::Vector->new(map {$_->value} @$c_B); +$c_4 = $t->current_tableau; + + +my $Badj = ($t->{B}->det) * ($t->{B}->inverse); +my $current_tableau = $Badj * $t->{M}; # the A | S | obj | b + +$correction_coeff = ($c_B2*$current_tableau)->row(1); +$obj_row_normalized = ($t->{B}->det) *$t->{obj_row}; +$current_coeff = $obj_row_normalized-$correction_coeff ; + +TEXT("obj_row ", $t->{obj_row}, $BR ); +TEXT("c_b is", @$c_B,$BR); +TEXT("c_b2 is", $c_B2,$BR); +TEXT("current coeff ", List($current_coeff),$BR); +BEGIN_PGML +matrix is [`[$m]`] + +b is [$b] + +and c is [$c] + +original tableau is [`[$t->{M}]`] + +and basis is [$t->basis] + +B is [`[$t->{B}]`] with determinant [$t->{B}->det] + +the objective row is [@ $t->{obj_row} @] + + +the coefficients associated with the basis are [@ List(@$c_B) @] + +the vector version of these coefficients is [$c_B2] + +the normalized objective row is [@ List($obj_row_normalized ) @] + +The correction coeff are [@ List($correction_coeff) @] + +The current coeff are [@ List($current_coeff) @] + +Print the current total tableau for the basis [@ Matrix($t->{basis}) @]: + +$t->current_tableau [`[$c_4]`] + +Here is the decorated version of the total tableau: + +[`[@ lp_display_mm($c_4, top_labels=>[qw(x1 x2 x3 x4 x5 x6 x7 w b)], +side_labels=>['\text{constraintA}', '', '\text{constraintC}', '\text{constraintD}', +'\text{objective_function}'])@]`] + + +END_PGML + +TEXT("array reference", pretty_print( convert_to_array_ref($c_4) )); +ENDDOCUMENT(); + From 334315b43b8b215ffb372e4b3af6bdd67f857dec Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Sat, 14 Oct 2017 10:34:07 -0400 Subject: [PATCH 20/70] Fix checks for scalar multiplication to not have to stringify the value if it is already a Real or Complex number. --- lib/Value/Matrix.pm | 5 ++--- lib/Value/Point.pm | 6 ++---- lib/Value/Vector.pm | 6 ++---- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/lib/Value/Matrix.pm b/lib/Value/Matrix.pm index f230931256..d03b696e43 100644 --- a/lib/Value/Matrix.pm +++ b/lib/Value/Matrix.pm @@ -209,7 +209,7 @@ sub mult { # # Constant multiplication # - if (Value::matchNumber($r) || Value::isComplex($r)) { + if (Value::isNumber($r)) { my @coords = (); foreach my $x (@{$l->data}) {push(@coords,$x*$r)} return $self->make(@coords); @@ -247,8 +247,7 @@ sub mult { sub div { my ($l,$r,$flag) = @_; my $self = $l; Value::Error("Can't divide by a Matrix") if $flag; - Value::Error("Matrices can only be divided by Numbers") - unless (Value::matchNumber($r) || Value::isComplex($r)); + Value::Error("Matrices can only be divided by Numbers") unless Value::isNumber($r); Value::Error("Division by zero") if $r == 0; my @coords = (); foreach my $x (@{$l->data}) {push(@coords,$x/$r)} diff --git a/lib/Value/Point.pm b/lib/Value/Point.pm index 6ab10d25bf..9d508ef265 100644 --- a/lib/Value/Point.pm +++ b/lib/Value/Point.pm @@ -81,8 +81,7 @@ sub sub { sub mult { my ($l,$r) = @_; my $self = $l; - Value::Error("Points can only be multiplied by Numbers") - unless (Value::matchNumber($r) || Value::isComplex($r)); + Value::Error("Points can only be multiplied by Numbers") unless Value::isNumber($r); my @coords = (); foreach my $x ($l->value) {push(@coords,$x*$r)} return $self->make(@coords); @@ -91,8 +90,7 @@ sub mult { sub div { my ($l,$r,$flag) = @_; my $self = $l; Value::Error("Can't divide by a Point") if $flag; - Value::Error("Points can only be divided by Numbers") - unless (Value::matchNumber($r) || Value::isComplex($r)); + Value::Error("Points can only be divided by Numbers") unless Value::isNumber($r); Value::Error("Division by zero") if $r == 0; my @coords = (); foreach my $x ($l->value) {push(@coords,$x/$r)} diff --git a/lib/Value/Vector.pm b/lib/Value/Vector.pm index 16116a2dab..52ac31f79f 100644 --- a/lib/Value/Vector.pm +++ b/lib/Value/Vector.pm @@ -92,8 +92,7 @@ sub sub { sub mult { my ($l,$r,$flag) = @_; my $self = $l; - Value::Error("Vectors can only be multiplied by Numbers") - unless (Value::matchNumber($r) || Value::isComplex($r)); + Value::Error("Vectors can only be multiplied by Numbers") unless Value::isNumber($r); my @coords = (); foreach my $x ($l->value) {push(@coords,$x*$r)} return $self->make(@coords); @@ -102,8 +101,7 @@ sub mult { sub div { my ($l,$r,$flag) = @_; my $self = $l; Value::Error("Can't divide by a Vector") if $flag; - Value::Error("Vectors can only be divided by Numbers") - unless (Value::matchNumber($r) || Value::isComplex($r)); + Value::Error("Vectors can only be divided by Numbers") unless Value::isNumber($r); Value::Error("Division by zero") if $r == 0; my @coords = (); foreach my $x ($l->value) {push(@coords,$x/$r)} From fcf8c080aa4696ea28141575c770a204bcde3d6a Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Wed, 25 Oct 2017 21:27:38 -0400 Subject: [PATCH 21/70] Fix reduction rule for x^(-1) to not reduce matrices, and extend it to x^(-a) for any constant power --- lib/Parser/BOP/power.pm | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/Parser/BOP/power.pm b/lib/Parser/BOP/power.pm index 78c1ca6403..f60999c316 100644 --- a/lib/Parser/BOP/power.pm +++ b/lib/Parser/BOP/power.pm @@ -49,8 +49,12 @@ sub _reduce { if (($self->{rop}{isZero} && !$self->{lop}{isZero} && $reduce->{'x^0'}) || ($self->{lop}{isOne} && $reduce->{'1^x'})); return $self->{lop} if $self->{rop}{isOne} && $reduce->{'x^1'}; - if ($self->{rop}->isNeg && $self->{rop}->string eq '-1' && $reduce->{'x^(-1)'}) { - $self = $self->Item("BOP")->new($equation,'/',$self->Item("Number")->new($equation,1),$self->{lop}); + if ($self->{rop}->isNeg && $self->{rop}{isConstant} && + $self->{lop}->typeRef->{name} ne "Matrix" && $reduce->{'x^(-a)'}) { + my $copy = $self->copy($equation); + $copy->{rop} = $self->Item("Number")->new($equation,-($copy->{rop}->eval)); + $self = $self->Item("BOP")->new($equation,'/',$self->Item("Number")->new($equation,1), + ($copy->{rop}->string eq '1' ? $copy->{lop} : $copy)); $self = $self->reduce; } return $self; @@ -58,7 +62,7 @@ sub _reduce { $Parser::reduce->{'x^0'} = 1; $Parser::reduce->{'1^x'} = 1; -$Parser::reduce->{'x^(-1)'} = 1; +$Parser::reduce->{'x^(-a)'} = 1; $Parser::reduce->{'x^1'} = 1; From 1082884e51b6cfda6a0760afccb3d728ddf589cf Mon Sep 17 00:00:00 2001 From: Michael Gage Date: Fri, 17 Nov 2017 17:37:34 -0500 Subject: [PATCH 22/70] Remove commented out code. Accepting the recommendations of the reviewer. Thanks @dsteinmo --- lib/PGcore.pm | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/PGcore.pm b/lib/PGcore.pm index 7db209cad4..a5dbaede25 100755 --- a/lib/PGcore.pm +++ b/lib/PGcore.pm @@ -527,7 +527,6 @@ sub store_persistent_data { # will store strings only (so far) my $self = shift; my $label = shift; my @content = @_; - # $self->internal_debug_message("PGcore::store_persistent_data: storing $label in PERSISTENCE_HASH"); if (defined($self->{PERSISTENCE_HASH}->{$label}) ) { warn "can' overwrite $label in persistent data"; } else { From 1c3a88fb1a88fbebafcd4b254b035ec8eb9938da Mon Sep 17 00:00:00 2001 From: Florian Heiderich Date: Mon, 20 Nov 2017 19:55:51 +0100 Subject: [PATCH 23/70] fix spelling: Matices -> Matrices --- lib/Value/Matrix.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Value/Matrix.pm b/lib/Value/Matrix.pm index d03b696e43..d3609a6c79 100644 --- a/lib/Value/Matrix.pm +++ b/lib/Value/Matrix.pm @@ -225,7 +225,7 @@ sub mult { if (scalar(@dl) == 1) {@dl = (1,@dl); $l = $self->make($l)} if (scalar(@dr) == 1) {@dr = (@dr,1); $r = $self->make($r)->transpose} Value::Error("Can only multiply 2-dimensional matrices") if scalar(@dl) > 2 || scalar(@dr) > 2; - Value::Error("Matices of dimensions %dx%d and %dx%d can't be multiplied",@dl,@dr) + Value::Error("Matrices of dimensions %dx%d and %dx%d can't be multiplied",@dl,@dr) unless ($dl[1] == $dr[0]); # # Do matrix multiplication From e455781fbdd2d4df2712f339757cd122dd92d50c Mon Sep 17 00:00:00 2001 From: John Jones Date: Mon, 18 Sep 2017 11:04:24 -0700 Subject: [PATCH 24/70] Tidying up addition of customizeLaTeX --- macros/PGstandard.pl | 3 ++ macros/customizeLaTeX.pl | 84 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 macros/customizeLaTeX.pl diff --git a/macros/PGstandard.pl b/macros/PGstandard.pl index 1805136772..a6598259bb 100644 --- a/macros/PGstandard.pl +++ b/macros/PGstandard.pl @@ -21,6 +21,8 @@ =head1 DESCRIPTION =item * PGauxiliaryFunctions.pl +=item * customizeLaTeX.pl + =back =cut @@ -30,6 +32,7 @@ =head1 DESCRIPTION "PGbasicmacros.pl", "PGanswermacros.pl", "PGauxiliaryFunctions.pl", + "customizeLaTeX.pl", ); 1; diff --git a/macros/customizeLaTeX.pl b/macros/customizeLaTeX.pl new file mode 100644 index 0000000000..bbd09e8209 --- /dev/null +++ b/macros/customizeLaTeX.pl @@ -0,0 +1,84 @@ +=head1 NAME + +customizeLaTeX.pl - Defines default LaTeX constructs for certain mathematical + ideas. + +=head1 DESCRIPTION + +The functions are loaded by default. Any/all can be overridden +in your course's PGcourse.pl +=cut + +##### Set theory macros +sub set_minus{ + #return "\\setminus"; + return '-'; +}; + +##### Logic macros +sub negate { + return "\\sim"; + #return "\\lnot"; +}; + +sub implies { + return "\\Rightarrow"; +} + +##### Algebra macros + +sub cyclic { + + my $n = shift; + + # leave one of the following return commands uncommented, depending on what notation you want to use for finite cyclic groups (e.g., Z/nZ) + + # display order n cyclic group as Z_n + return "\\mathbb{Z}_{$n}", + + # display order n cyclic group as C_n + # return "C_{$n}"; + + # display order n cyclic group as Z/nZ + # return "\\mathbb{Z}/{$n}\\mathbb{Z}"; + +}; + +sub dihedral { + + my $n = shift; + + # if you want to display dihedral groups as D_n (for instance, D_4 is the dihedral group of order 8), then leave this subroutine unmodified + + + # if you want to display dihedral groups as D_{2n} (for instance, D_8 is the dihedral group of order 8), then uncomment this set of if/else statements. The regular expression conditionals are to make sure it handles different types of arguments correctly. + # if( "$n" =~ m/^\s*(\d+)\s*$/ ) + # { + # $n = 2 * $1; + # } + # elsif( "$n" =~ m/^\s*(\w+)\s*$/ ) + # { + # $n = "2$1"; + # } + # else + # { + # $n = "2($n)"; + # } + + return "D_{$n}"; + +}; + +sub quaternions { + + # if you want to display the Quaternion group as Q_8, then leave this subroutine unmodified + + return "Q_8" + + # Alternatives + + # return "H_8" + # return "Q" +}; + +1; From 7b6585d10f0cb0ad4dec51079701fac4eb39c4c7 Mon Sep 17 00:00:00 2001 From: John Jones Date: Sun, 24 Dec 2017 10:29:32 -0700 Subject: [PATCH 25/70] Fix a typo and change defaults --- macros/customizeLaTeX.pl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/macros/customizeLaTeX.pl b/macros/customizeLaTeX.pl index bbd09e8209..eea50b302b 100644 --- a/macros/customizeLaTeX.pl +++ b/macros/customizeLaTeX.pl @@ -17,12 +17,13 @@ sub set_minus{ ##### Logic macros sub negate { - return "\\sim"; + return "\\mathbin{\\sim}"; #return "\\lnot"; }; sub implies { - return "\\Rightarrow"; + return "\\implies"; + #return "\\Rightarrow"; } ##### Algebra macros @@ -34,7 +35,7 @@ sub cyclic { # leave one of the following return commands uncommented, depending on what notation you want to use for finite cyclic groups (e.g., Z/nZ) # display order n cyclic group as Z_n - return "\\mathbb{Z}_{$n}", + return "\\mathbb{Z}_{$n}"; # display order n cyclic group as C_n # return "C_{$n}"; From 6230c75b4335de0e88a0481f1f7c64e6ca2ebd1e Mon Sep 17 00:00:00 2001 From: John Jones Date: Tue, 26 Dec 2017 07:45:11 -0700 Subject: [PATCH 26/70] Add init function to prevent file from being loaded twice, and revise logical not --- macros/customizeLaTeX.pl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/macros/customizeLaTeX.pl b/macros/customizeLaTeX.pl index eea50b302b..95ed66a601 100644 --- a/macros/customizeLaTeX.pl +++ b/macros/customizeLaTeX.pl @@ -9,6 +9,10 @@ =head1 DESCRIPTION in your course's PGcourse.pl =cut +sub _customizeLaTeX_init { + +} #prevents this file from being loaded twice. + ##### Set theory macros sub set_minus{ #return "\\setminus"; @@ -17,7 +21,7 @@ sub set_minus{ ##### Logic macros sub negate { - return "\\mathbin{\\sim}"; + return "{\\sim}"; #return "\\lnot"; }; From 983873142a2486d024d6cb8f3484af5df6a58bc1 Mon Sep 17 00:00:00 2001 From: John Jones Date: Tue, 26 Dec 2017 13:45:36 -0700 Subject: [PATCH 27/70] Added more macros based on suggestions in the OPL. --- macros/customizeLaTeX.pl | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/macros/customizeLaTeX.pl b/macros/customizeLaTeX.pl index 95ed66a601..6065599d86 100644 --- a/macros/customizeLaTeX.pl +++ b/macros/customizeLaTeX.pl @@ -21,7 +21,7 @@ sub set_minus{ ##### Logic macros sub negate { - return "{\\sim}"; + return "\\mathbin{\\sim}"; #return "\\lnot"; }; @@ -30,6 +30,14 @@ sub implies { #return "\\Rightarrow"; } +##### Linear algebra macros + +sub vectorstyle { + my $v = shift; + #return "\\vec\{$v\}" + return "$v"; +} + ##### Algebra macros sub cyclic { @@ -49,6 +57,12 @@ sub cyclic { }; +# Macro to display the ring Z/nZ +sub ZmodnZ { + my $n = shift; + return "\\mathbb{Z} / $n \\mathbb{Z}"; +} + sub dihedral { my $n = shift; From b6989cbe8d63dcf8f9771fa39e7e7fb900fc522a Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Wed, 27 Dec 2017 12:15:29 -0500 Subject: [PATCH 28/70] Improve error messages for non-invertible matrices --- lib/Parser/BOP.pm | 17 +++++++++++++++++ lib/Parser/BOP/power.pm | 4 +++- lib/Value/Matrix.pm | 12 +++++++++--- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/lib/Parser/BOP.pm b/lib/Parser/BOP.pm index 0950f64b01..ba3aa95ad2 100644 --- a/lib/Parser/BOP.pm +++ b/lib/Parser/BOP.pm @@ -175,6 +175,23 @@ sub checkMatrixSize { } else {$self->Error("Matrices are too deep to be multiplied")} } +# +# Check if a matrix is square +# +sub checkMatrixSquare { + my $self = shift; + my $m = shift; my $type = $m->{entryType}; + if ($type->{entryType}{name} eq 'Number') { + my ($r,$c) = ($m->{length},$type->{length}); + if ($r == $c) { + my $rowType = Value::Type('Matrix',$r,$Value::Type{number},formMatrix=>1); + $self->{type} = Value::Type('Matrix',$r,$rowType,formMatrix=>1); + return 1; + } + } + return 0; +} + # # Promote point operands to vectors or matrices. # diff --git a/lib/Parser/BOP/power.pm b/lib/Parser/BOP/power.pm index 78c1ca6403..6802d055c4 100644 --- a/lib/Parser/BOP/power.pm +++ b/lib/Parser/BOP/power.pm @@ -17,7 +17,9 @@ sub _check { return if $self->checkNumbers(); my ($ltype,$rtype) = $self->promotePoints('Matrix'); if ($rtype->{name} eq 'Number') { - if ($ltype->{name} eq 'Matrix') {$self->checkMatrixSize($ltype,$ltype)} + if ($ltype->{name} eq 'Matrix') { + $self->Error("Only square matrices can be raised to a power") if !$self->checkMatrixSquare($ltype); + } elsif ($self->context->flag("allowBadOperands")) {$self->{type} = $Value::Type{number}} else {$self->Error("You can only raise a Number to a power")} } diff --git a/lib/Value/Matrix.pm b/lib/Value/Matrix.pm index f230931256..29159e8833 100644 --- a/lib/Value/Matrix.pm +++ b/lib/Value/Matrix.pm @@ -260,7 +260,10 @@ sub power { Value::Error("Can't use Matrices in exponents") if $flag; Value::Error("Only square matrices can be raised to a power") unless $l->isSquare; $r = Value::makeValue($r,context=>$context); - if ($r->isNumber && $r =~ m/^-\d+$/) {$l = $l->inverse; $r = -$r} + if ($r->isNumber && $r =~ m/^-\d+$/) { + $l = $l->inverse; $r = -$r; + $self->Error("Matrix is not invertible") unless defined($l); + } Value::Error("Matrix powers must be non-negative integers") unless $r->isNumber && $r =~ m/^\d+$/; return $context->Package("Matrix")->I($l->length,$context) if $r == 0; my $M = $l; foreach my $i (2..$r) {$M = $M*$l} @@ -436,7 +439,8 @@ sub det { sub inverse { my $self = shift; $self->wwMatrixLR; Value->Error("Can't take inverse of non-square matrix") unless $self->isSquare; - return $self->new($self->{lrM}->invert_LR); + my $I = $self->{lrM}->invert_LR; + return (defined($I) ? $self->new($I) : $I); } sub decompose_LR { @@ -477,7 +481,9 @@ sub solve_LR { my $self = shift; my $v = $self->wwColumnVector(shift); my ($d,$b,$M) = $self->wwMatrixLR->solve_LR($v); - return ($d,$self->new($b),$self->new($M)); + $b = $self->new($b) if defined($b); + $M = $self->new($M) if defined($M); + return ($d,$b,$M); } sub condition { From 00ce5f416097596aafa5f2ded36fc41dc1efe397 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Wed, 27 Dec 2017 13:49:07 -0500 Subject: [PATCH 29/70] Add support for ions and states to the Reaciton context, and update the heavy atoms to their official names. --- macros/contextReaction.pl | 153 +++++++++++++++++++++++++++++++++++--- 1 file changed, 142 insertions(+), 11 deletions(-) diff --git a/macros/contextReaction.pl b/macros/contextReaction.pl index d37faa81ad..63de15a5fd 100644 --- a/macros/contextReaction.pl +++ b/macros/contextReaction.pl @@ -23,6 +23,19 @@ =head1 DESCRIPTION $R = Formula("4P + 5O_2 --> 2P_2O_5"); +Ions can be specified using ^ to produce superscripts, as in Na^+1 or +Na^{+1}. Note that the charge must be listed with prefix notation +(+1), not postfix notation (1+), and that a number is required (so you +can't use just Na^+). + +States can be appended to compounds, as in AgCl(s). So you can +make reactions like the following: + + Ag^{+1}(aq) + Cl^{-1}(aq) --> AgCl(s) + +Note that a state can be given by itself, e.g., (l), so you can ask +for a student to supply just a state. + Reactions know how to create their own TeX versions (via $R->TeX), and know how to check student answers (via $R->cmp), just like any other MathObject. @@ -53,16 +66,27 @@ =head1 DESCRIPTION different and unequal in this context. All the elements of the periodic table are available within the -Reaction Context. If you need additional terms, like "Heat" for -example, you can add them as variables: +Reaction Context, as are the states (aq), (s), (l), (g), and (ppt). +If you need additional terms, like "Heat" for example, you can add +them as variables: Context()->variables->add(Heat => $context::Reaction::CONSTANT); Then you can make formulas that include Heat as a term. These -"constants" are not allowed to have coefficients or subscripts, and -can not be combined with compounds except by addition. If you want a -term that can be combined in those ways, use -$context::Reaction::ELEMENT instead. +"constants" are not allowed to have coefficients or sub- or +superscripts, and can not be combined with compounds except by +addition. If you want a term that can be combined in those ways, use +$context::Reaction::ELEMENT instead, as in + + Context()->variables->add(e => $context::Reaction::ELEMENT); + +to allow "e" for electrons, for example. + +If you need to add more states, use $context::Reaction::STATE, as in + + Context()->variables->add('(x)' => $context::Reaction::STATE); + +to allow a state of (x) for a compound. =cut @@ -82,15 +106,18 @@ package context::Reaction; # our $ELEMENT = {isValue => 1, type => Value::Type("Element",1)}; our $MOLECULE = {isValue => 1, type => Value::Type("Molecule",1)}; +our $ION = {isValue => 1, type => Value::Type("Ion",1)}; our $COMPOUND = {isValue => 1, type => Value::Type("Compound",1)}; our $REACTION = {isValue => 1, type => Value::Type("Reaction",1)}; our $CONSTANT = {isValue => 1, type => Value::Type("Constant",1)}; +our $STATE = {isValue => 1, type => Value::Type("State",1)}; # # Set up the context and Reaction() constructor # sub Init { my $context = $main::context{Reaction} = Parser::Context->getCopy("Numeric"); + $context->{name} = "Reaction"; $context->functions->clear(); $context->strings->clear(); $context->constants->clear(); @@ -108,7 +135,7 @@ sub Init { '-->' => {precedence => 1, associativity => 'left', type => 'bin', string => ' --> ', class => 'context::Reaction::BOP::arrow', TeX => " \\longrightarrow "}, - '+' => {precedence => 2, associativity => 'left', type => 'bin', string => ' + ', + '+' => {precedence => 2, associativity => 'left', type => 'both', string => ' + ', class => 'context::Reaction::BOP::add', isComma => 1}, ' ' => {precedence => 3, associativity => 'left', type => 'bin', string => ' ', @@ -117,11 +144,17 @@ sub Init { '_' => {precedence => 4, associativity => 'left', type => 'bin', string => '_', class => 'context::Reaction::BOP::underscore'}, - '-' => {precedence => 5, associativity => 'left', type => 'both', string => ' - ', + '^' => {precedence => 4, associativity => 'left', type => 'bin', string => '^', + class => 'context::Reaction::BOP::superscript'}, + + '-' => {precedence => 5, associativity => 'left', type => 'both', string => '-', class => 'Parser::BOP::undefined'}, 'u-'=> {precedence => 6, associativity => 'left', type => 'unary', string => '-', - class => 'Parser::UOP::undefined', hidden => 1}, + class => 'context::Reaction::UOP::minus', hidden => 1}, + 'u+'=> {precedence => 6, associativity => 'left', type => 'unary', string => '+', + class => 'context::Reaction::UOP::plus', hidden => 1}, ); + $context->variables->{namePattern} = qr/\(?[a-zA-Z][a-zA-Z0-9]*\)?/; $context->variables->are( map {$_ => $ELEMENT} ( "H", "He", @@ -130,18 +163,26 @@ sub Init { "K", "Ca", "Sc","Ti","V", "Cr","Mn","Fe","Co","Ni","Cu","Zn","Ga","Ge","As","Se","Br","Kr", "Rb","Sr", "Y", "Zr","Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn","Sb","Te","I", "Xe", "Cs","Ba", "Lu","Hf","Ta","W", "Re","Os","Ir","Pt","Au","Hg","Ti","Pb","Bi","Po","At","Rn", - "Fr","Ra", "Lr","Rf","Db","Sg","Bh","Hs","Mt","Ds","Rg","Cn","Uut","Uuq","Uup","Uuh","Uus","Uuo", + "Fr","Ra", "Lr","Rf","Db","Sg","Bh","Hs","Mt","Ds","Rg","Cn","Nh","Fl","Mc","Lv","Ts","Og", "La","Ce","Pr","Nd","Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb", "Ac","Th","Pa","U", "Np","Pu","Am","Cm","Bk","Cf","Es","Fm","Md","No", ) ); + $context->variables->add( + map {$_ => $STATE} ( + "(aq)", "(s)", "(l)", "(g)", "(ppt)", + ) + ); + $context->reductions->clear(); + $context->flags->set(reduceConstants => 0); $context->{parser}{Number} = "context::Reaction::Number"; $context->{parser}{Variable} = "context::Reaction::Variable"; $context->{parser}{Formula} = "context::Reaction"; $context->{value}{Reaction} = "context::Reaction"; $context->{value}{Element} = "context::Reaction::Variable"; $context->{value}{Constant} = "context::Reaction::Variable"; + $context->{value}{State} = "context::Reaction::Variable"; Parser::Number::NoDecimals($context); main::PG_restricted_eval('sub Reaction {Value->Package("Formula")->new(@_)};'); @@ -254,7 +295,7 @@ sub TeX { # sub TYPE { my $self = shift; - return ($self->type eq 'Constant'? "'$self->{name}'" : 'an element'); + return ($self->type eq 'Constant' || $self->type eq 'State' ? 'a state' : 'an element'); } ###################################################################### @@ -357,11 +398,14 @@ sub _check { $self->Error("Can't combine %s and %s",$self->{lop}->TYPE,$self->{rop}->TYPE) unless ($self->{lop}->class eq 'Number' || $self->{lop}->isChemical) && $self->{rop}->isChemical; + $self->Error("Compound already has a state") + if $self->{lop}{hasState} && $self->{rop}->type eq 'State'; $self->Error("Can't combine %s with %s",$self->{lop}{name},$self->{rop}->TYPE) if $self->{lop}->type eq 'Constant'; $self->Error("Can't combine %s with %s",$self->{lop}->TYPE,$self->{rop}{name}) if $self->{rop}->type eq 'Constant'; $self->{type} = $COMPOUND->{type}; + $self->{hasState} = 1 if $self->{rop}->type eq 'State'; } # @@ -419,6 +463,93 @@ sub string { sub TYPE {'a molecule'} +###################################################################### +# +# Implements the superscript for creating ions +# +package context::Reaction::BOP::superscript; +our @ISA = ('context::Reaction::BOP'); + +# +# Check that the operands are OK +# +sub _check { + my $self = shift; + $self->Error("The left-hand side of '^' must be an element or molecule, not %s",$self->{lop}->TYPE) + unless $self->{lop}->type eq 'Element' || $self->{lop}->type eq 'Molecule'; + $self->Error("The right-hand side of '^' must be a signed number, not %s",$self->{rop}->TYPE) + unless $self->{rop}->class eq 'UOP'; + $self->{type} = $ION->{type}; +} + +# +# Create proper TeX output +# +sub TeX { + my $self = shift; + my $left = $self->{lop}->TeX; + return $left."^{".$self->{rop}->TeX."}"; +} + +# +# Create proper text output +# +sub string { + my $self = shift; + my $left = $self->{lop}->string; + return $left."^".$self->{rop}->string; +} + +sub TYPE {'an ion'} + +###################################################################### +# +# General unary operator (minus and plus are subclasses of this). +# +package context::Reaction::UOP; +our @ISA = ('Parser::UOP'); + +sub _check { + my $self = shift; + return if ($self->checkNumber); + $self->{type} = $Value::Type{number}; +} + +# +# Unary operators produce numbers +# +sub isChemical {0} + +sub eval {context::Reaction::eval(@_)} + +# +# Two nodes are equivalent if their operands are equivalent +# and they have the same operator +# +sub equivalent { + my $self = shift; my $other = shift; + return 0 unless $other->class eq 'UOP'; + return 0 unless $self->{uop} eq $other->{uop}; + return $self->{op}->equivalent($other->{op}); +} + +sub TYPE {'a signed number'}; + +###################################################################### +# +# Negative numbers (for ion exponents) +# +package context::Reaction::UOP::minus; +our @ISA = ('context::Reaction::UOP'); + +###################################################################### +# +# Positive numbers (for ion exponents) +# +package context::Reaction::UOP::plus; +our @ISA = ('context::Reaction::UOP'); + + ###################################################################### # # Implements sums of compounds as a list From 660d94d7f2ebcfd02332bdeb5d68b1cd2281f6e7 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Thu, 28 Dec 2017 08:09:13 -0500 Subject: [PATCH 30/70] Make sure initial context is Numeric, with values copied from PG environment --- macros/Parser.pl | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/macros/Parser.pl b/macros/Parser.pl index 6deddb8441..43a1c1b9bc 100644 --- a/macros/Parser.pl +++ b/macros/Parser.pl @@ -132,12 +132,10 @@ =head2 Context # ^uses Parser::Context::current # ^uses %context sub Context {Parser::Context->current(\%context,@_)} -unless (%context && $context{current}) { - # ^variable our %context - %context = (); # Locally defined contexts, including 'current' context - # ^uses Context - Context(); # Initialize context (for persistent mod_perl) -} +# # ^variable our %context +%context = () unless %context; # Locally defined contexts, including 'current' context +# ^uses Context +Context("Numeric"); # Set initial context ########################################################################### # From ac8b081d521df1e4141d88fa2ba2cb65747a1248 Mon Sep 17 00:00:00 2001 From: Michael Gage Date: Sat, 30 Dec 2017 12:36:16 -0500 Subject: [PATCH 31/70] Commit changes to documentation for PGmatrixmacros.pl --- macros/PGmatrixmacros.pl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/macros/PGmatrixmacros.pl b/macros/PGmatrixmacros.pl index c40d183b74..b34fd8b47f 100644 --- a/macros/PGmatrixmacros.pl +++ b/macros/PGmatrixmacros.pl @@ -650,7 +650,8 @@ sub ra_flatten_matrix{ \@array; } -= head4 apl_matrix_mult() + +=head4 apl_matrix_mult() # This subroutine is probably obsolete and not generally useful. # It was patterned after the APL From 28ba783bb344204b994862870d389dbf8d0028ad Mon Sep 17 00:00:00 2001 From: Michael Gage Date: Sat, 30 Dec 2017 12:46:14 -0500 Subject: [PATCH 32/70] remove files related to tableau from this PR. --- macros/tableau.pl | 535 ------------------------ t/matrix_tableau_tests/matrix_test1.pg | 111 ----- t/matrix_tableau_tests/tableau_test1.pg | 104 ----- 3 files changed, 750 deletions(-) delete mode 100755 macros/tableau.pl delete mode 100644 t/matrix_tableau_tests/matrix_test1.pg delete mode 100644 t/matrix_tableau_tests/tableau_test1.pg diff --git a/macros/tableau.pl b/macros/tableau.pl deleted file mode 100755 index bb4c7514d6..0000000000 --- a/macros/tableau.pl +++ /dev/null @@ -1,535 +0,0 @@ -#!/usr/bin/perl -w - -# this file needs documentation and unit testing. -# where is it used? - -##### From gage_matrix_ops -# 2014_HKUST_demo/templates/setSequentialWordProblem/bill_and_steve.pg:"gage_matrix_ops.pl", - -=head1 Tableaus and matrices - - # We're going to have several types - # MathObject Matrices Value::Matrix - # tableaus form John Jones macros - # MathObject tableaus - # Containing an matrix $A coefficients for constraint - # A vertical vector $b for constants for constraints - # A horizontal vector $c for coefficients for objective function - # A vertical vector corresponding to the value $z of the objective function - # dimensions $n problem vectors, $m constraints = $m slack variables - # A basis Value::Set -- positions for columns which are independent and - # whose associated variables can be determined - # uniquely from the parameter variables. - # The non-basis (parameter) variables are set to zero. - # - # state variables (assuming parameter variables are zero or when given parameter variables) - # create the methods for updating the various containers - # create the method for printing the tableau with all its decorations - # possibly with switches to turn the decorations on and off. - - -The structure of the tableau is: - - ----------------------------------------------------------- - | | | | | - | A | S | 0 | b | - | | | | | - ---------------------------------------------- - | -c | 0 | 1 | 0 | - ---------------------------------------------- - Matrix A, the constraint matrix is n by m - Matrix S, the slack variables is m by m - Matrix b, the constraint constants is n by 1 - The next to the last column holds z or objective value - z(...x^i...) = c_i* x^i (Einstein summation convention) - - -=cut - -=head2 Package main - -=cut - -=item get_tableau_variable_values - - Parameters: ($MathObjectMatrix_tableau, $MathObjectSet_basis) - Returns: ARRAY or ARRAY_ref - -Returns the solution variables to the tableau assuming -that the parameter (non-basis) variables -have been set to zero. It returns a list in -array context and a reference to -an array in scalar context. - -=item lp_basis_pivot - - Parameters: ($old_tableau,$old_basis,$pivot) - Returns: ($new_tableau, Set($new_basis),\@statevars) - -=item linebreak_at_commas - - Parameters: () - Return: - - Useage: - $foochecker = $constraints->cmp()->withPostFilter( - linebreak_at_commas() - ); - -Replaces commas with line breaks in the latex presentations of the answer checker. -Used most often when $constraints is a LinearInequality math object. - - - -=head2 Package tableau - -=item Tableau->new(A=>Matrix, b=>Vector or Matrix, c=>Vector or Matrix) - - A => undef, # constraint matrix MathObjectMatrix - b => undef, # constraint constants Vector or MathObjectMatrix 1 by n - c => undef, # coefficients for objective function Vector or MathObjectMatrix 1 by n - obj_row => undef, # contains the negative of the coefficients of the objective function. - z => undef, # value for objective function - n => undef, # dimension of problem variables (columns in A) - m => undef, # dimension of slack variables (rows in A) - S => undef, # square m by m matrix for slack variables - basis => undef, # list describing the current basis columns corresponding to determined variables. - B => undef, # square invertible matrix corresponding to the current basis columns - M => undef, # matrix of consisting of all columns and all rows except for the objective function row - obj_col_num => undef, - # flag indicating the column (1 or n+m+1) for the objective value - constraint_labels => undef, - problem_var_labels => undef, - slack_var_labels => undef, - -=item $self->current_tableau - Parameters: () - Returns: A MathObjectMatrix_tableau - -This represents the current version of the tableau - -=item $self->objective_row - Parameters: () - Returns: - -=item $self->basis - Parameter: ARRAY or ARRAY_ref or () - Returns: MathObject_list - - FiXME -- this should accept a MathObject_List (or MO_Set?) - -=head3 Package Tableau (eventually package Matrix?) - -=item $self->row_slice - - Parameter: @slice or \@slice - Return: MathObject matrix - -=item $self->extract_rows - - Parameter: @slice or \@slice - Return: two dimensional array ref - -=item extract_rows_to_list - - Parameter: @slice or \@slice - Return: MathObject List of row references - -=item $self->extract_columns - - Parameter: @slice or \@slice - Return: two dimensional array ref - -=item $self->column_slice - - Parameter: @slice or \@slice - Return: MathObject Matrix - -=item $self->extract_columns_to_list - - Parameter: @slice or \@slice - Return: MathObject List of Matrix references ? - -=item $self->submatrix - - Parameter:(rows=>\@row_slice,columns=>\@column_slice) - Return: MathObject matrix - - -=cut - - -sub _tableau_init {}; # don't reload this file -package main; - -sub matrix_column_slice{ - matrix_from_matrix_cols(@_); -} -sub matrix_from_matrix_cols { - my $M = shift; # a MathObject matrix_columns - my($n,$m) = $M->dimensions; - my @slice = @_; - if (ref($slice[0]) =~ /ARRAY/) { # handle array reference - @slice = @{$slice[0]}; - } - @slice = @slice?@slice : (1..$m); - my @columns = map {$M->column($_)->transpose->value} @slice; - #create the chosen columns as rows - # then transform to array_refs. - Matrix(@columns)->transpose; #transpose and return an n by m matrix (2 dim) -} -sub matrix_row_slice{ - matrix_from_matrix_rows(@_); -} - -sub matrix_from_matrix_rows { - my $M = shift; # a MathObject matrix_columns - my($n,$m) = $M->dimensions; - my @slice = @_; - if (ref($slice[0]) =~ /ARRAY/) { # handle array reference - @slice = @{$slice[0]}; - } - @slice = @slice? @slice : (1..$n); # the default is the whole matrix. - # DEBUG_MESSAGE("row slice in matrix from rows is @slice"); - my @rows = map {[$M->row($_)->value]} @slice; - #create the chosen columns as rows - # then transform to array_refs. - Matrix([@rows]); # insure that it is still an n by m matrix (2 dim) -} - -sub matrix_extract_submatrix { - matrix_from_submatrix(@_); -} -sub matrix_from_submatrix { - my $M=shift; - return undef unless ref($M) =~ /Value::Matrix/; - my %options = @_; - my($n,$m) = $M->dimensions; - my $row_slice = ($options{rows})?$options{rows}:[1..$m]; - my $col_slice = ($options{columns})?$options{columns}:[1..$n]; - # DEBUG_MESSAGE("ROW SLICE", join(" ", @$row_slice)); - # DEBUG_MESSAGE("COL SLICE", join(" ", @$col_slice)); - my $M1 = matrix_from_matrix_rows($M,@$row_slice); - # DEBUG_MESSAGE("M1 - matrix from rows) $M1"); - return matrix_from_matrix_cols($M1, @$col_slice); -} -sub matrix_extract_rows { - my $M =shift; - my @slice = @_; - if (ref($slice[0]) =~ /ARRAY/) { # handle array reference - @slice = @{$slice[0]}; - } elsif (@slice == 0) { # export all rows to List - @slice = ( 1..(($M->dimensions)[0]) ); - } - return map {$M->row($_)} @slice ; -} - -sub matrix_rows_to_list { - List(matrix_extract_rows(@_)); -} -sub matrix_columns_to_list { - List(matrix_extract_columns(@_) ); -} -sub matrix_extract_columns { - my $M =shift; # Add error checking - my @slice = @_; - if (ref($slice[0]) =~ /ARRAY/) { # handle array reference - @slice = @{$slice[0]}; - } elsif (@slice == 0) { # export all columns to an array - @slice = 1..($M->dimensions->[1]); - } - return map {$M->column($_)} @slice; -} - - - -######################## -############## -# get_tableau_variable_values -# -# Calculates the values of the basis variables of the tableau, -# assuming the parameter variables are 0. -# -# Usage: ARRAY = get_tableau_variable_values($MathObjectMatrix_tableau, $MathObjectSet_basis) -# -# feature request -- for tableau object -- allow specification of non-zero parameter variables -sub get_tableau_variable_values { - my $mat = shift; # a MathObject matrix - my $basis =shift; # a MathObject set - # FIXME - # type check ref($mat)='Matrix'; ref($basis)='Set'; - # or check that $mat has dimensions, element methods; and $basis has a contains method - my ($n, $m) = $mat->dimensions; - @var = (); - #DEBUG_MESSAGE( "start new matrix"); - foreach my $j (1..$m-2) { # the last two columns of the tableau are object variable and constants - if (not $basis->contains($j)) { - DEBUG_MESSAGE( "j= $j not in basis"); # set the parameter values to zero - $var[$j-1]=0; next; # non-basis variables (parameters) are set to 0. - - } else { - foreach my $i (1..$n-1) { # the last row is the objective function - # if this is a basis column there should be only one non-zero element(the pivot) - if ( $mat->element($i,$j)->value != 0 ) { # should this have ->value????? - $var[$j-1] = ($mat->element($i,$m)/($mat->element($i,$j))->value); - DEBUG_MESSAGE("i=$i j=$j var = $var[$j-1] "); # calculate basis variable value - next; - } - - } - } - } # element($n, $m-1) is the coefficient of the objective value. - # this last variable is the value of the objective function - push @var , ($mat->element($n,$m)/$mat->element($n,$m-1))->value; - - return wantarray ? @var : \@var; -} -#### Test -- assume matrix is this -# 1 2 1 0 0 | 0 | 3 -# 4 5 0 1 0 | 0 | 6 -# 7 8 0 0 1 | 0 | 9 -# -1 -2 0 0 0 | 1 | 10 # objective row -# and basis is {3,4,5} (start columns with 1) -# $n= 4; $m = 7 -# $x1=0; $x2=0; $x3=s1=3; $x4=s2=6; $x5=s3=9; w=10=objective value -# -# - -#################################### -# -# Cover for lp_pivot which allows us to use a set object for the new and old basis - -sub lp_basis_pivot { - my ($old_tableau,$old_basis,$pivot) = @_; # $pivot is a Value::Point - my $new_tableau= lp_clone($old_tableau); - # lp_pivot has 0 based indices - main::lp_pivot($new_tableau, $pivot->extract(1)-1,$pivot->extract(2)-1); - # lp_pivot pivots in place - my $new_matrix = Matrix($new_tableau); - my ($n,$m) = $new_matrix->dimensions; - my $param_size = $m-$n -1; #n=constraints+1, #m = $param_size + $constraints +2 - my $new_basis = ( $old_basis - ($pivot->extract(1)+$param_size) + ($pivot->extract(2)) )->sort; - my @statevars = get_tableau_variable_values($new_matrix, $new_basis); - return ( $new_tableau, Set($new_basis),\@statevars); - # force to set (from type Union) to insure that ->data is an array and not a string. -} - - - -sub linebreak_at_commas { - return sub { - my $ans=shift; - my $foo = $ans->{correct_ans_latex_string}; - $foo =~ s/,/,\\\\\\\\/g; - ($ans->{correct_ans_latex_string})=~ s/,/,\\\\\\\\/g; - ($ans->{preview_latex_string})=~ s/,/,\\\\\\\\/g; - #DEBUG_MESSAGE("foo", $foo); - #DEBUG_MESSAGE( "correct", $ans->{correct_ans_latex_string} ); - #DEBUG_MESSAGE( "preview", $ans->{preview_latex_string} ); - #DEBUG_MESSAGE("section4ans1 ", pretty_print($ans, $displayMode)); - $ans; - }; -} -# Useage -# $foochecker = $constraints->cmp()->withPostFilter( -# linebreak_at_commas() -# ); - - -### End gage_matrix_ops include - - - -################################################## -package Tableau; -our @ISA = qw(Value::Matrix Value); - -sub _Matrix { # can we just import this? - # this is a function, not a method - Value::Matrix->new(@_); -} - -sub new { - my $self = shift; my $class = ref($self) || $self; - my $context = (Value::isContext($_[0]) ? shift : $self->context); - my $tableau = { - A => undef, # constraint matrix MathObjectMatrix - b => undef, # constraint constants Vector or MathObjectMatrix 1 by n - c => undef, # coefficients for objective function Vector or MathObjectMatrix 1 by n - obj_row => undef, # contains the negative of the coefficients of the objective function. - z => undef, # value for objective function - n => undef, # dimension of problem variables (columns in A) - m => undef, # dimension of slack variables (rows in A) - S => undef, # square m by m matrix for slack variables - basis => undef, # list describing the current basis columns corresponding to determined variables. - B => undef, # square invertible matrix corresponding to the current basis columns - M => undef, # matrix of consisting of all columns and all rows except for the objective function row and column - obj_col_num => undef, # flag indicating the column (1 or n+m+1) for the objective value - constraint_labels => undef, - problem_var_labels => undef, - slack_var_labels => undef, - @_, - }; - bless $tableau, $class; - $tableau->initialize(); - return $tableau; -} - -# the following are used to construct the tableau -# initialize -# assemble_matrix -# objective_row -sub initialize { - $self= shift; - unless (ref($self->{A}) =~ /Value::Matrix/ && - ref($self->{b}) =~ /Value::Vector|Value::Matrix/ && - ref($self->{c}) =~ /Value::Vector|Value::Matrix/){ - main::WARN_MESSAGE("Error: Required inputs: Tableau(A=> Matrix, b=>Vector, c=>Vector)"); - return; - } - my ($m, $n)=($self->{A}->dimensions); - $self->{n}=$self->{n}//$n; - $self->{m}=$self->{m}//$m; - # main::DEBUG_MESSAGE("m $m, n $n"); - $self->{S} = Value::Matrix->I($m); - $self->{basis} = [($n+1)...($n+$m)] unless ref($self->{basis})=~/ARRAY/; - my @rows = $self->assemble_matrix; - #main::DEBUG_MESSAGE("rows", @rows); - $self->{M} = _Matrix([@rows]); - $self->{B} = $self->{M}->submatrix(rows=>[1..($self->{m})],columns=>$self->{basis}); - $self->{obj_row} = _Matrix($self->objective_row()); - return(); -} - -sub assemble_matrix { - my $self = shift; - my @rows =(); - my $m = $self->{m}; - my $n = $self->{n}; - foreach my $i (1..$m) { - my @current_row=(); - foreach my $j (1..$n) { - push @current_row, $self->{A}->element($i, $j); - } - foreach my $j (1..$m) { - push @current_row, $self->{S}->element($i,$j); # slack variables - } - push @current_row, 0, $self->{b}->data->[$i-1]; # obj column and constant column - push @rows, [@current_row]; - } - - return @rows; # these are the matrices A | S | obj | b - # the final row describing the objective function is not in this -} - -sub objective_row { - my $self = shift; - my @last_row=(); - push @last_row, ( -($self->{c}) )->value; - foreach my $i (1..($self->{m})) { push @last_row, 0 }; - push @last_row, 1, 0; - return \@last_row; -} - -# return a matrix containing the entire tableau -sub current_tableau { - my $Badj = ($self->{B}->det) * ($self->{B}->inverse); - my $current_tableau = $Badj * $self->{M}; # the A | S | obj | b - $self->{current_tableau}=$current_tableau; - # find the coefficients associated with the basis columns - my $c_B = $self->{obj_row}->extract_columns($self->{basis} ); - my $c_B2 = Value::Vector->new([ map {$_->value} @$c_B]); - my $correction_coeff = ($c_B2*$current_tableau )->row(1); - # subtract the correction coefficients from the obj_row - # this essentially extends Gauss reduction applied to the obj_row - my $obj_row_normalized = ($self->{B}->det) *$self->{obj_row}; - my $current_coeff = $obj_row_normalized-$correction_coeff ; - $self->{current_coeff}= $current_coeff; - - #main::DEBUG_MESSAGE("subtract these two ", (($self->{B}->det) *$self->{obj_row}), " | ", ($c_B*$current_tableau)->dimensions); - #main::DEBUG_MESSAGE("all coefficients", join('|', $self->{obj_row}->value ) ); - #main::DEBUG_MESSAGE("current coefficients", join('|', @current_coeff) ); - #main::DEBUG_MESSAGE("type of $self->{basis}", ref($self->{basis}) ); - #main::DEBUG_MESSAGE("current basis",join("|", @{$self->{basis}})); - #main::DEBUG_MESSAGE("CURRENT STATE ", $current_tableau); - return _Matrix( @{$current_tableau->extract_rows},$self->{current_coeff} ); - #return( $self->{current_coeff} ); -} - -sub basis { - my $self = shift; #update basis - my @input = @_; - return Value::List->new($self->{basis}) unless @input; #return basis if no input - my $new_basis; - if (ref( $input[0]) =~/ARRAY/) { - $new_basis=$input[0]; - } else { - $new_basis = \@input; - } - $self->{basis}= $new_basis; - $self->{B} = $self->{M}->submatrix(rows=>[1..($self->{m})],columns=>$self->{basis}); - return Value::List->new($self->{basis}); -} - - - - -package Value::Matrix; - -sub _Matrix { - Value::Matrix->new(@_); -} - -sub row_slice { - $self = shift; - @slice = @_; - return _Matrix( $self->extract_rows(@slice) ); -} -sub extract_rows { - $self = shift; - my @slice = @_; - if (ref($slice[0]) =~ /ARRAY/) { # handle array reference - @slice = @{$slice[0]}; - } elsif (@slice == 0) { # export all rows to List - @slice = ( 1..(($self->dimensions)[0]) ); - } - return [map {$self->row($_)} @slice ]; #prefer to pass references when possible -} -sub column_slice { - $self = shift; - return _Matrix( $self->extract_columns(@_) )->transpose; # matrix is built as rows then transposed. -} -sub extract_columns { - $self = shift; - my @slice = @_; - if (ref($slice[0]) =~ /ARRAY/) { # handle array reference - @slice = @{$slice[0]}; - } elsif (@slice == 0) { # export all columns to an array - @slice = ( 1..(($self->dimensions)[1] ) ); - } - return [map { $self->transpose->row($_) } @slice] ; - # returns the columns as an array of 1 by n row matrices containing values - # if you pull columns directly you get an array of 1 by n column vectors. - # prefer to pass references when possible -} -sub extract_rows_to_list { - my $self = shift; - Value::List->new($self->extract_rows(@_)); -} -sub extract_columns_to_list { - my $self = shift; - Value::List->new($self->extract_columns(@_) ); -} - -sub submatrix { - my $self = shift; - my %options = @_; - my($m,$n) = $self->dimensions; - my $row_slice = ($options{rows})?$options{rows}:[1..$m]; - my $col_slice = ($options{columns})?$options{columns}:[1..$n]; - return $self->row_slice($row_slice)->column_slice($col_slice); -} - - - -1; diff --git a/t/matrix_tableau_tests/matrix_test1.pg b/t/matrix_tableau_tests/matrix_test1.pg deleted file mode 100644 index c9b2c5f3bc..0000000000 --- a/t/matrix_tableau_tests/matrix_test1.pg +++ /dev/null @@ -1,111 +0,0 @@ -############################################## -DOCUMENT(); - -loadMacros( - "PGstandard.pl", # Standard macros for PG language - "MathObjects.pl", - "parserLinearInequality.pl", - "PGML.pl", - "tableau.pl", - "PGmatrixmacros.pl", - "LinearProgramming.pl", - #"source.pl", # allows code to be displayed on certain sites. - "PGcourse.pl", -); - -############################################## - -Context("Matrix"); # need Matrix context to allow string input into Matrix. - -#Construct a small test matrix. -$m = Matrix("[[3,6,7],[2,1,8],[4,6,21],[-6,7,9]]"); - - - $m_rows = $m->extract_rows([4,1]); #outputs an array reference - $m_cols = $m->extract_columns([3,2,1]); #outputs the columns as rows - $m1list = List($m_rows); - $m2list = List($m_cols); - $list_cols = $m->extract_columns_to_list(1,2); - $list_rows = $m->extract_rows_to_list(2,3); - - $m1matrix = Matrix($m_rows); - $m2matrix = Matrix($m_cols)->transpose; #matrix is built with rows and then needs to be transposed - - - $m3 = $m->row_slice([4,1]); - $m4 = $m->column_slice([3,2,1]); - - $submatrix1 = $m->submatrix(rows=>[2,3],columns=>[1,2]); - $submatrix2 = $m->submatrix(rows=>[2,3]); - $submatrix3 = $m->submatrix(columns=>[1,2]); - $submatrix4 = $m->submatrix(); - - -BEGIN_PGML - -Create a matrix: matrix m = [`[$m]`] - -Matrix indices start at 1. - -Extracting rows and columns from a matrix. -These are best displayed in PGML within a list. -It is best to place them in the list outside of PGML and then display. - -You can also group the extracted rows into a matrix and display. - -Matrix m = [`[$m]`] - -List rows 4,1 of the matrix m :[` [@ List($m_rows) @] `], - -or create the list before hand [`[$m1list]`] - -or matrix version [`[$m1matrix]`] - -You can do the same for columns. Notice that this does not -do exactly what you expect when you extract columns - -matrix m = [`[$m]`] - -List columns 3,2,1 of the matrix m: [@ List($m_cols) @] - -create the list before hand [`[$m2list]`] - -or create matrix version [`[$m2matrix]`] - - -Using the \[@ ... @\] escape to apply List doesn't -always do the best job -of interacting with TeX output as you can see in the first entries for List above. - -The two entries below show what happens if you extract rows or -columns into lists using -the method $m->extract_rows_to_list(). -It is the same as applying List() to the method -$m->extract_rows outside of PGML. - -matrix: [`[$m]`] - -Find the list of columns 1,2 of the matrix [`[$list_cols]`] - -Find the list of rows 2,3 of the matrix [`[$list_rows]`] - -The next two entries illustrate the $m->row_slice, $m->column_slice methods for shuffling or -selecting rows or columns from a matrix. The return type is Value::Matrix. The column -selection is done by doing row selection on the transpose -- this does what you expect -when selecting columns. - -Row slice (4,1) [`[$m3]`] - -Column slice (3,2,1) [`[$m4]`] - -This final group selects a rectangular submatrix of a matrix. ->submatrix. - -Select \[2,3\]x\[1,2\] to get [`[$submatrix1]`] of [`[$m]`] - -Select \[2,3\]x all to get [`[$submatrix2]`] - -Select all x \[1,2\] to get [`[$submatrix3]`] - -END_PGML - -ENDDOCUMENT(); \ No newline at end of file diff --git a/t/matrix_tableau_tests/tableau_test1.pg b/t/matrix_tableau_tests/tableau_test1.pg deleted file mode 100644 index cc0a503fe7..0000000000 --- a/t/matrix_tableau_tests/tableau_test1.pg +++ /dev/null @@ -1,104 +0,0 @@ - - - -############################################## -DOCUMENT(); - -loadMacros( - "PGstandard.pl", # Standard macros for PG language - "MathObjects.pl", - "parserLinearInequality.pl", - "PGML.pl", - "tableau.pl", - "PGmatrixmacros.pl", - "LinearProgramming.pl", - #"source.pl", # allows code to be displayed on certain sites. - "PGcourse.pl", -); - -############################################## - -Context("Matrix"); # need Matrix context to allow string input into Matrix. - -$m = Matrix("[[3,6,7],[2,1,8],[4,6,21],[-6,7,9]]"); -$constraint_matrix = Matrix(" -[[ 0, 0, -1, -1], - [-1, -1, 0, 0 ], - [1, 0 , 1 , 0], - [0, 1, 0, 1]] -"); - -#TEXT ("created ". ref($m)); -#what are the best ways to display a matrix? - -$m1 = $m->row_slice([4,1]); -$m2 = $m->column_slice([3,2,1]); - -$list = $m->extract_rows_to_list(2,3); - -$b = Matrix([1, 2, 3, 4]); -#TEXT($BR, "vector", $b->data->[1]); -$c = Matrix([5, 6, 7]); -$t = Tableau->new(A=>$m,b=>$b, c=>$c); - -$basis2 = $t->basis(1,3,4,6); -$t->current_tableau; - -$c_B = $t->{obj_row}->extract_columns($t->{basis} ); #basis coefficients -$c_B2 = Value::Vector->new(map {$_->value} @$c_B); -$c_4 = $t->current_tableau; - - -my $Badj = ($t->{B}->det) * ($t->{B}->inverse); -my $current_tableau = $Badj * $t->{M}; # the A | S | obj | b - -$correction_coeff = ($c_B2*$current_tableau)->row(1); -$obj_row_normalized = ($t->{B}->det) *$t->{obj_row}; -$current_coeff = $obj_row_normalized-$correction_coeff ; - -TEXT("obj_row ", $t->{obj_row}, $BR ); -TEXT("c_b is", @$c_B,$BR); -TEXT("c_b2 is", $c_B2,$BR); -TEXT("current coeff ", List($current_coeff),$BR); -BEGIN_PGML -matrix is [`[$m]`] - -b is [$b] - -and c is [$c] - -original tableau is [`[$t->{M}]`] - -and basis is [$t->basis] - -B is [`[$t->{B}]`] with determinant [$t->{B}->det] - -the objective row is [@ $t->{obj_row} @] - - -the coefficients associated with the basis are [@ List(@$c_B) @] - -the vector version of these coefficients is [$c_B2] - -the normalized objective row is [@ List($obj_row_normalized ) @] - -The correction coeff are [@ List($correction_coeff) @] - -The current coeff are [@ List($current_coeff) @] - -Print the current total tableau for the basis [@ Matrix($t->{basis}) @]: - -$t->current_tableau [`[$c_4]`] - -Here is the decorated version of the total tableau: - -[`[@ lp_display_mm($c_4, top_labels=>[qw(x1 x2 x3 x4 x5 x6 x7 w b)], -side_labels=>['\text{constraintA}', '', '\text{constraintC}', '\text{constraintD}', -'\text{objective_function}'])@]`] - - -END_PGML - -TEXT("array reference", pretty_print( convert_to_array_ref($c_4) )); -ENDDOCUMENT(); - From 2911251868a582b66ccffbb17133a35b0fe6c0db Mon Sep 17 00:00:00 2001 From: John Jones Date: Sat, 30 Dec 2017 12:59:30 -0700 Subject: [PATCH 33/70] Change default for vector --- macros/customizeLaTeX.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/macros/customizeLaTeX.pl b/macros/customizeLaTeX.pl index 6065599d86..99e308972c 100644 --- a/macros/customizeLaTeX.pl +++ b/macros/customizeLaTeX.pl @@ -34,8 +34,8 @@ sub implies { sub vectorstyle { my $v = shift; - #return "\\vec\{$v\}" - return "$v"; + return "\\vec{$v}" + #return "$v"; } ##### Algebra macros From 4486abbc9890c7cb0de99e8d754183d6b36e2f01 Mon Sep 17 00:00:00 2001 From: Michael Gage Date: Sat, 30 Dec 2017 22:09:04 -0500 Subject: [PATCH 34/70] Accepting changes suggest by Davide Cervone --- lib/Matrix.pm | 17 ++++++++++------- lib/Value/Matrix.pm | 41 +++++++++++++++++++++++++++------------- macros/MatrixReduce.pl | 4 +++- macros/PGmatrixmacros.pl | 2 +- 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/lib/Matrix.pm b/lib/Matrix.pm index b2f83ce052..3bb7586c94 100644 --- a/lib/Matrix.pm +++ b/lib/Matrix.pm @@ -81,12 +81,16 @@ sub _stringify { L($matrix) - return matrix L of the LR decomposition R($matrix) - return matrix R of the LR decomposition - PL($matrix) return - PR($matrix + PL($matrix) - return permutation matrix + PR($matrix) - return permutation matrix + Original matrix is PL * L * R *PR = M + +Obtain the Left Right matrices of the decomposition +and the two pivot permutation matrices +the original is M = PL*L*R*PR + +=cut -Original matrix is P_L * L * R *P_R -# obtain the Left Right matrices of the decomposition and the two pivot permutation matrices -# the original is M = PL*L*R*PR sub L { my $matrix = shift; my $rows = $matrix->[1]; @@ -100,6 +104,7 @@ sub L { } $L_matrix; } + sub R { my $matrix = shift; my $rows = $matrix->[1]; @@ -134,8 +139,6 @@ sub PR { # use this permuation on the right PL*L*R*PR =M $PR_matrix; } -# obtain the Left Right matrices of the decomposition and the two pivot permutation matrices -# the original is M = PL*L*R*PR =head4 diff --git a/lib/Value/Matrix.pm b/lib/Value/Matrix.pm index 1c97ee713f..0d73cc32ec 100644 --- a/lib/Value/Matrix.pm +++ b/lib/Value/Matrix.pm @@ -21,17 +21,27 @@ L Files interacting with Matrices: L L + L L -- checking whether vectors form a basis + L -- tools for row reduction via elementary matrices + L -- Generates unimodular matrices with real entries + L + L + L + L + L + L + Contexts Matrix -- allows students to enter [[3,4],[3,6]] @@ -74,19 +84,22 @@ see C in MatrixReduce and L - trace - proj - proj_coeff - L - R - PL - PR + trace + proj + proj_coeff + L + R + PL + PR -Passthrough methods covering subroutines in MatrixReal1.pm +Passthrough methods covering subroutines in C (this has been modified to handle complex numbers) -The actual calculations are done in MatrixReal1.pm subroutines +The actual calculations are done in C subroutines +The commands below are Value::Matrix B unless otherwise noted. + + condition det @@ -98,8 +111,10 @@ The actual calculations are done in MatrixReal1.pm subroutines norm_max kleene normalize - solve_LR (also solve()) - order_LR (also order() + solve_LR($v) - LR decomposition + solve($M,$v) - function version of solve_LR + order_LR - order of LR decomposition matrix (number of non-zero equations)(also order() ) + order($M) - function version of order_LR solve_GSM solve_SSM solve_RM @@ -595,7 +610,7 @@ sub condition { } sub order {shift->order_LR(@_)} -sub order_LR { +sub order_LR { # order of LR decomposition matrix (number of non-zero equations) my $self = shift; return $self->wwMatrixLR->order_LR; } diff --git a/macros/MatrixReduce.pl b/macros/MatrixReduce.pl index a77925dc76..7769777a0f 100644 --- a/macros/MatrixReduce.pl +++ b/macros/MatrixReduce.pl @@ -28,7 +28,9 @@ =head1 SYNOPSIS changes the [2,3] entry to the value 50. -=item Construct an n x n identity matrix: C<$E = identity_matrix(5);> +=item Construct an n x n identity matrix: C<$E = identity_matrix(5);> + +(This is an alias for Value::Matrix->I(5);) =item Construct an n x n elementary matrix that will permute rows i and j: diff --git a/macros/PGmatrixmacros.pl b/macros/PGmatrixmacros.pl index b34fd8b47f..9daaa396fa 100644 --- a/macros/PGmatrixmacros.pl +++ b/macros/PGmatrixmacros.pl @@ -744,7 +744,7 @@ =head2 convert_to_array_ref { sub convert_to_array_ref { my $input = shift; - if (ref($input) eq 'Value::Matrix' ) { + if (Value::classMatch($input,"Matrix") { $input = [$input->value]; } elsif (ref($input) eq 'Matrix' ) { $input = $input->array_ref; From bfd0a5b307b7c233b3ead6e3bc8d25a4a7b69f02 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Fri, 29 Sep 2017 15:06:02 -0700 Subject: [PATCH 35/70] PTX support for WeBWorK --- lib/PGalias.pm | 2 +- lib/WeBWorK/PG/Translator.pm | 4 +- macros/AppletObjects.pl | 2 +- macros/CanvasObject.pl | 8 +- macros/PGML.pl | 130 ++++++++++++ macros/PGbasicmacros.pl | 326 +++++++++++++++++++++++-------- macros/PGchoicemacros.pl | 55 ++++-- macros/PGessaymacros.pl | 10 +- macros/PGmatrixmacros.pl | 22 ++- macros/alignedChoice.pl | 11 ++ macros/compoundProblem.pl | 2 +- macros/compoundProblem2.pl | 13 +- macros/compoundProblem5.pl | 7 +- macros/contextArbitraryString.pl | 2 +- macros/niceTables.pl | 34 +++- macros/parserPopUp.pl | 8 + macros/parserRadioButtons.pl | 6 +- macros/scaffold.pl | 11 +- macros/unionTables.pl | 9 + 19 files changed, 534 insertions(+), 128 deletions(-) diff --git a/lib/PGalias.pm b/lib/PGalias.pm index dcbbabe921..27597d37b6 100644 --- a/lib/PGalias.pm +++ b/lib/PGalias.pm @@ -265,7 +265,7 @@ sub make_alias { or $ext eq 'js' or $ext eq 'nb' ) { - if ($displayMode =~ /^HTML/ ) { + if ($displayMode =~ /^HTML/ or $displayMode eq 'PTX') { $adr_output=$self->alias_for_html($aux_file_id, $ext); } elsif ($displayMode eq 'TeX') { ################################################################################ diff --git a/lib/WeBWorK/PG/Translator.pm b/lib/WeBWorK/PG/Translator.pm index c3028b3b9f..1779bdc20c 100644 --- a/lib/WeBWorK/PG/Translator.pm +++ b/lib/WeBWorK/PG/Translator.pm @@ -1858,8 +1858,8 @@ sub default_preprocess_code { $evalString =~ s/\n\h*END_PGML_HINT[\h;]*\n/\nEND_PGML_HINT\n/g; $evalString =~ s/\n\h*END_SOLUTION[\h;]*\n/\nEND_SOLUTION\n/g; $evalString =~ s/\n\h*END_HINT[\h;]*\n/\nEND_HINT\n/g; - $evalString =~ s/\n\h*BEGIN_TEXT[\h;]*\n/\nTEXT\(EV3P\(<<'END_TEXT'\)\);\n/g; - $evalString =~ s/\n\h*BEGIN_PGML[\h;]*\n/\nTEXT\(PGML::Format2\(<<'END_PGML'\)\);\n/g; + $evalString =~ s/\n\h*BEGIN_TEXT[\h;]*\n/\nSTATEMENT\(EV3P\(<<'END_TEXT'\)\);\n/g; + $evalString =~ s/\n\h*BEGIN_PGML[\h;]*\n/\nSTATEMENT\(PGML::Format2\(<<'END_PGML'\)\);\n/g; $evalString =~ s/\n\h*BEGIN_PGML_SOLUTION[\h;]*\n/\nSOLUTION\(PGML::Format2\(<<'END_PGML_SOLUTION'\)\);\n/g; $evalString =~ s/\n\h*BEGIN_PGML_HINT[\h;]*\n/\nHINT\(PGML::Format2\(<<'END_PGML_HINT'\)\);\n/g; $evalString =~ s/\n\h*BEGIN_SOLUTION[\h;]*\n/\nSOLUTION\(EV3P\(<<'END_SOLUTION'\)\);\n/g; diff --git a/macros/AppletObjects.pl b/macros/AppletObjects.pl index 055df41fe4..ffedd09114 100644 --- a/macros/AppletObjects.pl +++ b/macros/AppletObjects.pl @@ -334,7 +334,7 @@ sub insertAll { ## inserts both header text and object text # Return HTML or TeX strings to be included in the body of the page ########################## - return main::MODES(TeX=>' {\bf applet } ', HTML=>$self->insertObject.$main::BR.$state_storage_html_code.$answerBox_code); + return main::MODES(TeX=>' {\bf applet } ', HTML=>$self->insertObject.$main::BR.$state_storage_html_code.$answerBox_code, PTX=>' applet '); } =head3 Example problem diff --git a/macros/CanvasObject.pl b/macros/CanvasObject.pl index fe77c19efd..4190ac2fa6 100644 --- a/macros/CanvasObject.pl +++ b/macros/CanvasObject.pl @@ -172,7 +172,7 @@ sub insertCanvas { my $myWidth = shift() || 200; my $myHeight = shift() ||200; - $canvasObject = MODES(TeX=>"canvasObject",HTML=><"canvasObject", PTX=>" canvas object ", HTML=>< var canvasWidth = $myWidth; var canvasHeight = $myHeight; END_CANVAS @@ -181,7 +181,7 @@ sub insertCanvas { } sub insertYvaluesInputBox { - $yValuesInput = MODES(TeX=>"yVAluesInput",HTML=><"yValuesInput", PTX=>" y-values input ", HTML=>< Y-values: @@ -193,7 +193,7 @@ sub insertYvaluesInputBox { } sub insertGridButtons { - $gridButtons = MODES(TeX=>"gridButtons",HTML=><"gridButtons", PTX=>" grid buttons ", HTML=><Toggle Grid @@ -406,7 +406,7 @@ sub insertGridButtons { # } sub insertPointsArea { - $pointsArea = MODES(TeX=>"pointsArea",HTML=><"pointsArea", PTX=>" points area ", HTML=><Get Points
EOF diff --git a/macros/PGML.pl b/macros/PGML.pl index d773fcdbd1..fe0ee332d9 100644 --- a/macros/PGML.pl +++ b/macros/PGML.pl @@ -1332,6 +1332,133 @@ sub Math { return main::math_ev3($self->SUPER::Math(@_)); } +###################################################################### +###################################################################### + +package PGML::Format::ptx; +our @ISA = ('PGML::Format'); + +sub Escape { + my $self = shift; + my $string = shift; return "" unless defined $string; + $string = main::PTX_special_character_cleanup($string); + return $string; +} + +# No indentation for PTX +sub Indent { + my $self = shift; my $item = shift; + return $self->string($item); +} + +# No align for PTX +sub Align { + my $self = shift; my $item = shift; + return $self->string($item); +} + +my %bullet = ( + bullet => 'ul', + numeric => 'ol label="1."', + alpha => 'ol label="a."', + Alpha => 'ol label="A."', + roman => 'ol label="i."', + Roman => 'ol label="I."', + disc => 'ul label="disc"', + circle => 'ul label="circle"', + square => 'ul label="square"', +); +sub List { + my $self = shift; my $item = shift; + my $list = $bullet{$item->{bullet}}; + return + $self->nl . + '<'.$list.'>'."\n" . + $self->string($item) . + $self->nl . + "\n"; +} + +sub Bullet { + my $self = shift; my $item = shift; + return $self->nl.'
  • '.$self->string($item).'
  • '; +} + +sub Code { + my $self = shift; my $item = shift; + my $class = ($item->{class} ? ' class="'.$item->{class}.'"' : ""); + return $self->nl . + "\n" . + join("<\/cline>\n", split(/\n/,$self->string($item))) . + "<\/cline>\n<\/cd>\n"; +} + +sub Pre { + my $self = shift; my $item = shift; + return + $self->nl . + '
    ' .
    +    $self->string($item) .
    +    "
    \n"; +} + +# PreTeXt can't use headings. +sub Heading { + my $self = shift; my $item = shift; + my $n = $item->{n}; + my $text = $self->string($item); + $text =~ s/^ +| +$//gm; $text =~ s! +(
    )!$1!g; + return $text."\n"; +} + +sub Par { + my $self = shift; my $item = shift; + return $self->nl."\n"; +} + +sub Break {"\n\n"} + +sub Bold { + my $self = shift; my $item = shift; + return ''.$self->string($item).''; +} + +sub Italic { + my $self = shift; my $item = shift; + return ''.$self->string($item).''; +} + +our %openQuote = ('"' => "", "'" => ""); +our %closeQuote = ('"' => "", "'" => ""); +sub Quote { + my $self = shift; my $item = shift; my $string = shift; + return $openQuote{$item->{token}} if $string eq "" || $string =~ m/(^|[ ({\[\s])$/; + return $closeQuote{$item->{token}}; +} + +# No rule for PTX +sub Rule { + my $self = shift; my $item = shift; + return $self->nl; +} + +sub Verbatim { + my $self = shift; my $item = shift; + #Don't escape most content. Just < and & + #my $text = $self->Escape($item->{text}); + my $text = $item->{text}; + $text =~ s/$text"; + return $text; +} + +sub Math { + my $self = shift; + return main::math_ev3($self->SUPER::Math(@_)); +} + + ###################################################################### ###################################################################### @@ -1343,6 +1470,9 @@ sub Format { my $format; if ($main::displayMode eq 'TeX') { $format = "{\\pgmlSetup\n".PGML::Format::tex->new($parser)->format."\\par}%\n"; + } elsif ($main::displayMode eq 'PTX') { + $format = PGML::Format::ptx->new($parser)->format."\n"; + $format = main::PTX_cleanup($format); } else { $format = '
    '."\n".PGML::Format::html->new($parser)->format.'
    '."\n"; } diff --git a/macros/PGbasicmacros.pl b/macros/PGbasicmacros.pl index b66f0e43a2..eb75b7ec7c 100644 --- a/macros/PGbasicmacros.pl +++ b/macros/PGbasicmacros.pl @@ -405,6 +405,7 @@ sub NAMED_ANS_RULE { HTML => qq!\n!. $add_html. # added for dragmath qq!\n!, + PTX => '', ); } @@ -445,7 +446,8 @@ sub NAMED_HIDDEN_ANS_RULE { # this is used to hold information being passed into TeX => "\\mbox{\\parbox[t]{${tcol}ex}{\\hrulefill}}", Latex2HTML => qq!\\begin{rawhtml}\\end{rawhtml}!, HTML => qq!!. - qq!! + qq!!, + PTX => '', ); } sub NAMED_ANS_RULE_OPTION { # deprecated @@ -495,7 +497,8 @@ sub NAMED_ANS_RULE_EXTENSION { TeX => "\\mbox{\\parbox[t]{${tcol}ex}{\\hrulefill}}", Latex2HTML => qq!\\begin{rawhtml}\n\n\\end{rawhtml}\n!, HTML => qq!!. - qq!! + qq!!, + PTX => '', ); } @@ -536,7 +539,8 @@ sub ANS_RULE { #deprecated HTML => qq! - ! + !, + PTX => '', ); $out; } @@ -571,7 +575,8 @@ sub NAMED_ANS_RADIO { MODES( TeX => qq!\\item{$tag}\n!, Latex2HTML => qq!\\begin{rawhtml}\n\\end{rawhtml}$tag!, - HTML => qq!! + HTML => qq!!, + PTX => '
  • '."$tag".'
  • '."\n", ); } @@ -610,7 +615,8 @@ sub NAMED_ANS_RADIO_EXTENSION { MODES( TeX => qq!\\item{$tag}\n!, Latex2HTML => qq!\\begin{rawhtml}\n\\end{rawhtml}$tag!, - HTML => qq!! + HTML => qq!!, + PTX => '
  • '."$tag".'
  • '."\n", ); } @@ -771,7 +777,8 @@ sub NAMED_ANS_CHECKBOX { MODES( TeX => qq!\\item{$tag}\n!, Latex2HTML => qq!\\begin{rawhtml}\n\\end{rawhtml}$tag!, - HTML => qq!! + HTML => qq!!, + PTX => '
  • '."$tag".'
  • '."\n", ); } @@ -808,7 +815,8 @@ sub NAMED_ANS_CHECKBOX_OPTION { MODES( TeX => qq!\\item{$tag}\n!, Latex2HTML => qq!\\begin{rawhtml}\n\\end{rawhtml}$tag!, - HTML => qq!! + HTML => qq!!, + PTX => '
  • '."$tag".'
  • '."\n", ); } @@ -891,6 +899,9 @@ sub ans_radio_buttons { if ($displayMode eq 'TeX') { $radio_buttons[0] = "\n\\begin{itemize}\n" . $radio_buttons[0]; $radio_buttons[$#radio_buttons] .= "\n\\end{itemize}\n"; + } elsif ($displayMode eq 'PTX') { + $radio_buttons[0] = '' . "\n" . $radio_buttons[0]; + $radio_buttons[$#radio_buttons] .= ''; } (wantarray) ? @radio_buttons: join(" ", @radio_buttons); @@ -904,6 +915,9 @@ sub ans_checkbox { if ($displayMode eq 'TeX') { $checkboxes[0] = "\n\\begin{itemize}\n" . $checkboxes[0]; $checkboxes[$#checkboxes] .= "\n\\end{itemize}\n"; + } elsif ($displayMode eq 'PTX') { + $checkboxes[0] = '' . "\n" . $checkboxes[0]; + $checkboxes[$#checkboxes] .= '' } (wantarray) ? @checkboxes: join(" ", @checkboxes); @@ -924,7 +938,8 @@ sub tex_ans_rule { 'Latex2HTML' => '\\fbox{Answer boxes cannot be placed inside typeset equations}', 'HTML_tth' => '\\begin{rawhtml} '. $answer_rule.'\\end{rawhtml}', 'HTML_dpng' => '\\fbox{Answer boxes cannot be placed inside typeset equations}', - 'HTML' => $answer_rule + 'HTML' => $answer_rule, + 'PTX' => 'Answer boxes cannot be placed inside typeset equations', ); $out; @@ -940,7 +955,8 @@ sub tex_ans_rule_extension { 'Latex2HTML' => '\fbox{Answer boxes cannot be placed inside typeset equations}', 'HTML_tth' => '\\begin{rawhtml} '. $answer_rule.'\\end{rawhtml}', 'HTML_dpng' => '\fbox{Answer boxes cannot be placed inside typeset equations}', - 'HTML' => $answer_rule + 'HTML' => $answer_rule, + 'PTX' => 'Answer boxes cannot be placed inside typeset equations', ); $out; @@ -956,7 +972,8 @@ sub NAMED_TEX_ANS_RULE { 'Latex2HTML' => '\\fbox{Answer boxes cannot be placed inside typeset equations}', 'HTML_tth' => '\\begin{rawhtml} '. $answer_rule.'\\end{rawhtml}', 'HTML_dpng' => '\\fbox{Answer boxes cannot be placed inside typeset equations}', - 'HTML' => $answer_rule + 'HTML' => $answer_rule, + 'PTX' => 'Answer boxes cannot be placed inside typeset equations', ); $out; @@ -971,7 +988,8 @@ sub NAMED_TEX_ANS_RULE_EXTENSION { 'Latex2HTML' => '\fbox{Answer boxes cannot be placed inside typeset equations}', 'HTML_tth' => '\\begin{rawhtml} '. $answer_rule.'\\end{rawhtml}', 'HTML_dpng' => '\fbox{Answer boxes cannot be placed inside typeset equations}', - 'HTML' => $answer_rule + 'HTML' => $answer_rule, + 'PTX' => 'Answer boxes cannot be placed inside typeset equations', ); $out; @@ -1029,6 +1047,13 @@ sub NAMED_POP_UP_LIST { $out .= " \\begin{rawhtml}\\end{rawhtml}\n"; } elsif ( $displayMode eq "TeX") { $out .= "\\fbox{?}"; + } elsif ( $displayMode eq "PTX") { + $out = '' . "\n"; + my $i; + foreach ($i=0; $i< @list; $i=$i+2) { + $out .= '
  • '.$list[$i+1].'
  • '."\n"; + }; + $out .= '
    '; } $name = RECORD_ANS_NAME($name,$answer_value); # record answer name $out; @@ -1055,6 +1080,9 @@ =head5 answer_matrix The options are passed on to display_matrix. + Note (7/21/2107) The above usage does not work. Omitting the \[ \] works, but also must + load PGmatrixmacros.pl to get display_matrix used below + =cut @@ -1135,7 +1163,8 @@ sub NAMED_ANS_ARRAY_EXTENSION{ MODES( TeX => "\\mbox{\\parbox[t]{10pt}{\\hrulefill}}\\hrulefill\\quad ", Latex2HTML => qq!\\begin{rawhtml}\n\n\\end{rawhtml}\n!, - HTML => qq!\n! + HTML => qq!\n!, + PTX => qq!!, ); } @@ -1205,7 +1234,7 @@ sub ans_array_extension{ # end answer blank macros -=head2 Hints and solutions macros +=head2 Hints, solutions, and statement macros solution('text','text2',...); SOLUTION('text','text2',...); # equivalent to TEXT(solution(...)); @@ -1213,6 +1242,12 @@ =head2 Hints and solutions macros hint('text', 'text2', ...); HINT('text', 'text2',...); # equivalent to TEXT("$BR$HINT" . hint(@_) . "$BR") if hint(@_); + statement('text'); + STATEMENT('text'); # equivalent to TEXT(statement(...)); + +statement takes a string, probably from EV3P, and possibly wraps opening and closing +content, paralleling one feature of solution and hint. + Solution prints its concatenated input when the check box named 'ShowSol' is set and the time is after the answer date. The check box 'ShowSol' is visible only after the answer date or when the problem is viewed by a professor. @@ -1272,12 +1307,14 @@ sub SOLUTION { base64 =>1 ) ) if solution(@_); } elsif ($displayMode=~/TeX/) { TEXT( - "\n%%% BEGIN SOLUTION\n", #Marker used in MathBook XML extraction; contact alex.jordan@pcc.edu before modifying + "\n%%% BEGIN SOLUTION\n", #Marker used in PreTeXt LaTeX extraction; contact alex.jordan@pcc.edu before modifying $PAR,SOLUTION_HEADING(), solution(@_).$PAR, - "\n%%% END SOLUTION\n" #Marker used in MathBook XML extraction; contact alex.jordan@pcc.edu before modifying + "\n%%% END SOLUTION\n" #Marker used in PreTeXt LaTeX extraction; contact alex.jordan@pcc.edu before modifying ) if solution(@_) ; } elsif ($displayMode=~/HTML/) { TEXT( $PAR.SOLUTION_HEADING().$BR.solution(@_).$PAR) if solution(@_) ; + } elsif ($displayMode=~/PTX/) { + TEXT( '',"\n",solution(@_),"\n",'',"\n\n") if solution(@_) ; } else { TEXT( $PAR.solution(@_).$PAR) if solution(@_) ; } @@ -1315,7 +1352,9 @@ sub hint { ## the second test above prevents a hint being shown if a doctored form is submitted $out = join(' ',@in); } - } + } elsif ($displayMode=~/PTX/) { + $out = join(' ',@in); + } $out ; } @@ -1327,17 +1366,31 @@ sub HINT { base64 => 1) ) if hint(@_); } elsif ($displayMode=~/TeX/) { TEXT( - "\n%%% BEGIN HINT\n", #Marker used in MathBook XML extraction; contact alex.jordan@pcc.edu before modifying + "\n%%% BEGIN HINT\n", #Marker used in PreTeXt LaTeX extraction; contact alex.jordan@pcc.edu before modifying $PAR,HINT_HEADING(), hint(@_).$PAR, - "\n%%% END HINT\n" #Marker used in MathBook XML extraction; contact alex.jordan@pcc.edu before modifying + "\n%%% END HINT\n" #Marker used in PreTeXt LaTeX extraction; contact alex.jordan@pcc.edu before modifying ) if hint(@_) ; + } elsif ($displayMode=~/PTX/) { + TEXT( '',"\n",hint(@_),"\n",'',"\n\n") if hint(@_); } else { TEXT($PAR, HINT_HEADING(), $BR. hint(@_) . $PAR) if hint(@_); } } -# End hints and solutions macros +sub statement { + my @in = @_; + my $out = join(' ',@in); + $out; +} + +sub STATEMENT { +if ($displayMode eq 'PTX') { TEXT('',"\n", statement(@_), "\n", '',"\n\n"); } + else { TEXT( statement(@_) ) }; +} + + +# End hints and solutions and statement macros ################################# =head2 Comments to instructors @@ -1453,6 +1506,7 @@ sub M3 { our %DISPLAY_MODE_FAILOVER = ( TeX => [], HTML => [], + PTX => [ "HTML" ], HTML_tth => [ "HTML", ], HTML_dpng => [ "HTML_tth", "HTML", ], HTML_jsMath => [ "HTML_dpng", "HTML_tth", "HTML", ], @@ -1554,22 +1608,22 @@ sub ALPHABET { # Some constants which are different in tex and in HTML # The order of arguments is TeX, Latex2HTML, HTML # Adopted Davide Cervone's improvements to PAR, LTS, GTS, LTE, GTE, LBRACE, RBRACE, LB, RB. 7-14-03 AKP -sub PAR { MODES( TeX => '\\par ', Latex2HTML => '\\begin{rawhtml}

    \\end{rawhtml}', HTML => '

    '); }; +sub PAR { MODES( TeX => '\\par ', Latex2HTML => '\\begin{rawhtml}

    \\end{rawhtml}', HTML => '

    ', PTX => "\n\n"); }; #sub BR { MODES( TeX => '\\par\\noindent ', Latex2HTML => '\\begin{rawhtml}
    \\end{rawhtml}', HTML => '
    '); }; # Alternate definition of BR which is slightly more flexible and gives more white space in printed output # which looks better but kills more trees. -sub BR { MODES( TeX => '\\leavevmode\\\\\\relax ', Latex2HTML => '\\begin{rawhtml}
    \\end{rawhtml}', HTML => '
    '); }; -sub BRBR { MODES( TeX => '\\leavevmode\\\\\\relax \\leavevmode\\\\\\relax ', Latex2HTML => '\\begin{rawhtml}

    \\end{rawhtml}', HTML => '

    '); }; -sub LQ { MODES( TeX => "\\lq\\lq{}", Latex2HTML => '"', HTML => '"' ); }; -sub RQ { MODES( TeX => "\\rq\\rq{}", Latex2HTML => '"', HTML => '"' ); }; -sub BM { MODES(TeX => '\\(', Latex2HTML => '\\(', HTML => ''); }; # begin math mode -sub EM { MODES(TeX => '\\)', Latex2HTML => '\\)', HTML => ''); }; # end math mode -sub BDM { MODES(TeX => '\\[', Latex2HTML => '\\[', HTML => '

    '); }; #begin displayMath mode -sub EDM { MODES(TeX => '\\]', Latex2HTML => '\\]', HTML => '

    '); }; #end displayMath mode -sub LTS { MODES(TeX => '<', Latex2HTML => '\\lt ', HTML => '<', HTML_tth => '<' ); }; -sub GTS { MODES(TeX => '>', Latex2HTML => '\\gt ', HTML => '>', HTML_tth => '>' ); }; -sub LTE { MODES(TeX => '\\le ', Latex2HTML => '\\le ', HTML => '<', HTML_tth => '\\le ' ); }; -sub GTE { MODES(TeX => '\\ge ', Latex2HTML => '\\ge ', HTML => '>', HTML_tth => '\\ge ' ); }; +sub BR { MODES( TeX => '\\leavevmode\\\\\\relax ', Latex2HTML => '\\begin{rawhtml}
    \\end{rawhtml}', HTML => '
    ', PTX => "\n\n"); }; +sub BRBR { MODES( TeX => '\\leavevmode\\\\\\relax \\leavevmode\\\\\\relax ', Latex2HTML => '\\begin{rawhtml}

    \\end{rawhtml}', HTML => '

    ', PTX => "\n"); }; +sub LQ { MODES( TeX => "\\lq\\lq{}", Latex2HTML => '"', HTML => '"', PTX => '' ); }; +sub RQ { MODES( TeX => "\\rq\\rq{}", Latex2HTML => '"', HTML => '"', PTX => '' ); }; +sub BM { MODES(TeX => '\\(', Latex2HTML => '\\(', HTML => '', PTX => ''); }; # begin math mode +sub EM { MODES(TeX => '\\)', Latex2HTML => '\\)', HTML => '', PTX => ''); }; # end math mode +sub BDM { MODES(TeX => '\\[', Latex2HTML => '\\[', HTML => '

    ', PTX => ''); }; #begin displayMath mode +sub EDM { MODES(TeX => '\\]', Latex2HTML => '\\]', HTML => '

    ', PTX => ''); }; #end displayMath mode +sub LTS { MODES(TeX => '<', Latex2HTML => '\\lt ', HTML => '<', HTML_tth => '<', PTX => '\lt' ); }; #only for use in math mode +sub GTS { MODES(TeX => '>', Latex2HTML => '\\gt ', HTML => '>', HTML_tth => '>', PTX => '\gt' ); }; #only for use in math mode +sub LTE { MODES(TeX => '\\le ', Latex2HTML => '\\le ', HTML => '<', HTML_tth => '\\le ', PTX => '\leq' ); }; #only for use in math mode +sub GTE { MODES(TeX => '\\ge ', Latex2HTML => '\\ge ', HTML => '>', HTML_tth => '\\ge ', PTX => '\geq' ); }; #only for use in math mode sub BEGIN_ONE_COLUMN { MODES(TeX => "\\ifdefined\\nocolumns\\else \\end{multicols}\\fi\n", Latex2HTML => " ", HTML => " "); }; sub END_ONE_COLUMN { MODES(TeX => " \\ifdefined\\nocolumns\\else \\begin{multicols}{2}\n\\columnwidth=\\linewidth \\fi\n", @@ -1578,34 +1632,35 @@ sub ALPHABET { }; sub SOLUTION_HEADING { MODES( TeX => '\\par {\\bf '.maketext('Solution:').' }', Latex2HTML => '\\par {\\bf '.maketext('Solution:').' }', - HTML => ''.maketext('Solution:').' '); + HTML => ''.maketext('Solution:').' ', + PTX => ''); }; -sub HINT_HEADING { MODES( TeX => "\\par {\\bf ".maketext('Hint:')." }", Latex2HTML => "\\par {\\bf ".maketext('Hint:')." }", HTML => "".maketext('Hint:')." "); }; -sub US { MODES(TeX => '\\_', Latex2HTML => '\\_', HTML => '_');}; # underscore, e.g. file${US}name -sub SPACE { MODES(TeX => '\\ ', Latex2HTML => '\\ ', HTML => ' ');}; # force a space in latex, doesn't force extra space in html -sub NBSP { MODES(TeX => '~', Latex2HTML => '~', HTML => ' ');}; -sub NDASH { MODES(TeX => '--', Latex2HTML => '--', HTML => '–');}; -sub MDASH { MODES(TeX => '---', Latex2HTML => '---', HTML => '—');}; -sub BBOLD { MODES(TeX => '{\\bf ', Latex2HTML => '{\\bf ', HTML => ''); }; -sub EBOLD { MODES( TeX => '}', Latex2HTML => '}',HTML => ''); }; -sub BLABEL { MODES(TeX => '', Latex2HTML => '', HTML => ''); }; -sub BITALIC { MODES(TeX => '{\\it ', Latex2HTML => '{\\it ', HTML => ''); }; -sub EITALIC { MODES(TeX => '} ', Latex2HTML => '} ', HTML => ''); }; -sub BUL { MODES(TeX => '\\underline{', Latex2HTML => '\\underline{', HTML => ''); }; -sub EUL { MODES(TeX => '}', Latex2HTML => '}', HTML => ''); }; -sub BCENTER { MODES(TeX => '\\begin{center} ', Latex2HTML => ' \\begin{rawhtml}
    \\end{rawhtml} ', HTML => '
    '); }; -sub ECENTER { MODES(TeX => '\\end{center} ', Latex2HTML => ' \\begin{rawhtml}
    \\end{rawhtml} ', HTML => '
    '); }; -sub BLTR { MODES(TeX => ' ', Latex2HTML => ' \\begin{rawhtml}
    \\end{rawhtml} ', HTML => ''); }; -sub ELTR { MODES(TeX => ' ', Latex2HTML => ' \\begin{rawhtml}
    \\end{rawhtml} ', HTML => ''); }; -sub HR { MODES(TeX => '\\par\\hrulefill\\par ', Latex2HTML => '\\begin{rawhtml}
    \\end{rawhtml}', HTML => '
    '); }; -sub LBRACE { MODES( TeX => '\{', Latex2HTML => '\\lbrace', HTML => '{' , HTML_tth=> '\\lbrace' ); }; -sub RBRACE { MODES( TeX => '\}', Latex2HTML => '\\rbrace', HTML => '}' , HTML_tth=> '\\rbrace',); }; -sub LB { MODES( TeX => '\{', Latex2HTML => '\\lbrace', HTML => '{' , HTML_tth=> '\\lbrace' ); }; -sub RB { MODES( TeX => '\}', Latex2HTML => '\\rbrace', HTML => '}' , HTML_tth=> '\\rbrace',); }; -sub DOLLAR { MODES( TeX => '\\$', Latex2HTML => '$', HTML => '$' ); }; -sub PERCENT { MODES( TeX => '\\%', Latex2HTML => '\\%', HTML => '%' ); }; -sub CARET { MODES( TeX => '\\verb+^+', Latex2HTML => '\\verb+^+', HTML => '^' ); }; +sub HINT_HEADING { MODES( TeX => "\\par {\\bf ".maketext('Hint:')." }", Latex2HTML => "\\par {\\bf ".maketext('Hint:')." }", HTML => "".maketext('Hint:')." ", PTX => ''); }; +sub US { MODES(TeX => '\\_', Latex2HTML => '\\_', HTML => '_', PTX => '');}; # underscore, e.g. file${US}name +sub SPACE { MODES(TeX => '\\ ', Latex2HTML => '\\ ', HTML => ' ', PTX => ' ');}; # force a space in latex, doesn't force extra space in html +sub NBSP { MODES(TeX => '~', Latex2HTML => '~', HTML => ' ', PTX => '');}; +sub NDASH { MODES(TeX => '--', Latex2HTML => '--', HTML => '–', PTX => '');}; +sub MDASH { MODES(TeX => '---', Latex2HTML => '---', HTML => '—', PTX => '');}; +sub BBOLD { MODES(TeX => '{\\bf ', Latex2HTML => '{\\bf ', HTML => '', PTX => ''); }; +sub EBOLD { MODES( TeX => '}', Latex2HTML => '}',HTML => '', PTX => ''); }; +sub BLABEL { MODES(TeX => '', Latex2HTML => '', HTML => '', PTX => ''); }; +sub BITALIC { MODES(TeX => '{\\it ', Latex2HTML => '{\\it ', HTML => '', PTX => ''); }; +sub EITALIC { MODES(TeX => '} ', Latex2HTML => '} ', HTML => '', PTX => ''); }; +sub BUL { MODES(TeX => '\\underline{', Latex2HTML => '\\underline{', HTML => '', PTX => ''); }; +sub EUL { MODES(TeX => '}', Latex2HTML => '}', HTML => '', PTX => ''); }; +sub BCENTER { MODES(TeX => '\\begin{center} ', Latex2HTML => ' \\begin{rawhtml}
    \\end{rawhtml} ', HTML => '
    ', PTX => ''); }; +sub ECENTER { MODES(TeX => '\\end{center} ', Latex2HTML => ' \\begin{rawhtml}
    \\end{rawhtml} ', HTML => '
    ', PTX => ''); }; +sub BLTR { MODES(TeX => ' ', Latex2HTML => ' \\begin{rawhtml}
    \\end{rawhtml} ', HTML => '', PTX => ''); }; +sub ELTR { MODES(TeX => ' ', Latex2HTML => ' \\begin{rawhtml}
    \\end{rawhtml} ', HTML => '', PTX => ''); }; +sub HR { MODES(TeX => '\\par\\hrulefill\\par ', Latex2HTML => '\\begin{rawhtml}
    \\end{rawhtml}', HTML => '
    ', PTX => ''); }; +sub LBRACE { MODES( TeX => '\{', Latex2HTML => '\\lbrace', HTML => '{' , HTML_tth=> '\\lbrace', PTX => '' ); }; #not for use in math mode +sub RBRACE { MODES( TeX => '\}', Latex2HTML => '\\rbrace', HTML => '}' , HTML_tth=> '\\rbrace', PTX => '' ); }; #not for use in math mode +sub LB { MODES( TeX => '\{', Latex2HTML => '\\lbrace', HTML => '{' , HTML_tth=> '\\lbrace', PTX => '' ); }; #not for use in math mode +sub RB { MODES( TeX => '\}', Latex2HTML => '\\rbrace', HTML => '}' , HTML_tth=> '\\rbrace', PTX => '' ); }; #not for use in math mode +sub DOLLAR { MODES( TeX => '\\$', Latex2HTML => '$', HTML => '$', PTX => '' ); }; +sub PERCENT { MODES( TeX => '\\%', Latex2HTML => '\\%', HTML => '%', PTX => '' ); }; +sub CARET { MODES( TeX => '\\verb+^+', Latex2HTML => '\\verb+^+', HTML => '^', PTX => '' ); }; sub PI {4*atan2(1,1);}; sub E {exp(1);}; @@ -1946,6 +2001,10 @@ sub general_math_ev3 { $in = HTML::Entities::encode_entities($in); $out = "`$in`" if $mode eq "inline"; $out = '
    `'.$in.'`
    ' if $mode eq "display"; + } elsif ($displayMode eq "PTX") { + $in = HTML::Entities::encode_entities($in); + $out = ''."$in".'' if $mode eq "inline"; + $out = ''."$in".'' if $mode eq "display"; } elsif ($displayMode eq "HTML_LaTeXMathML") { $in = HTML::Entities::encode_entities($in); $in = '{'.$in.'}'; @@ -2089,7 +2148,8 @@ sub EV3P { $string = ev_substring($string,"\\(","\\)",\&math_ev3); $string = ev_substring($string,"\\[","\\]",\&display_math_ev3); } - + + if ($displayMode eq 'PTX') {$string = PTX_cleanup($string)}; return $string; } @@ -2127,6 +2187,87 @@ sub EV3P_parser { } +sub PTX_cleanup { + my $string = shift; + # Wrap

    tags where necessary, and other cleanup + # Nothing else should be creating p tags, so assume all p tags created here + # The only supported top-level elements within a statement, hint, or solution in a problem + # are p, blockquote, pre, sidebyside + if ($displayMode eq 'PTX') { + #encase entire string in

    + $string = "

    ".$string."

    "; + + #a may have been created within a of a as a container of an + #so here we clean that up + $string =~ s/(?s)(((?!<\/cell>).)*?)]*>(.*?)<\/sidebyside>(.*?<\/cell>)/$1$3$4/g; + + #inside a sidebyside, the only permissible children are p, image, video, and tabular + #insert opening and closing p, to be removed later if they enclose an image, video or tabular + $string =~ s/(]*(?)/$1\n

    /g; + $string =~ s/(<\/sidebyside>)/<\/p>\n$1/g; + $string =~ s/(]*(?<=\/)>)/<\p>\n$1\n

    /g; + #ditto for li + $string =~ s/(]*(?)/$1\n

    /g; + $string =~ s/(<\/li>)/<\/p>\n$1/g; + $string =~ s/(]*(?<=\/)>)/<\p>\n$1\n

    /g; + + #close p right before any sidebyside, blockquote, or pre, image, video, or tabular + #and open p immediately following. Later any potential side effects are cleaned up. + $string =~ s/(<(sidebyside|blockquote|pre|image|video|tabular)[^>]*(?)/<\/p>\n$1/g; + $string =~ s/(<\/(sidebyside|blockquote|pre|image|video|tabular)>)/$1\n

    /g; + $string =~ s/(<(sidebyside|blockquote|pre|image|video|tabular)[^>]*(?<=\/)>)/<\/p>\n$1\n

    /g; + + #within a , we may have an issue if there was an image that had '<\p>' and '

    ' wrapped around + #it from the above block. If the '

    ' has a preceding '

    ' within the cell, no problem. Otherwise, + #the '

    ' must go. Likewise at the other end. + $string =~ s/(?s)(.*?)<\/p>\n(]*(?<=\/)>)\n

    (.*?<\/cell>)/$1$2$3/g; + + #remove blank lines; assume the intent was to end a p and start a new one + #but don't put closing and opening p replacing the blank lines if they precede or follow a + #or an

  • + $string =~ s/(\r\n?|\n)(\r\n?|\n)+(?!(|<\/tabular>|
  • |\r\n?|\n))/<\/p>\n

    /g; + $string =~ s/(\r\n?|\n)(\r\n?|\n)+(?=(|<\/tabular>|

  • |\r\n?|\n))/\n/g; + + #remove whitespace following

    + $string =~ s/(?s)(

    )\s*/$1/g; + + #remove whitespace preceding

    + $string =~ s/(?s)\s*(<\/p>)/$1/g; + + #remove empty p + $string =~ s/(\r\n?|\n)?

    <\/p>//g; + + #a tabular cell may have

    and

    but no corresponding width specification in a col. + #if so, remove all

    and

    from all cells. + my $previous; + do { + $previous = $string; + $string =~ s/(?s)((?:\s|/g; + $string =~ s/#//g; + $string =~ s/\$//g; + $string =~ s/\%//g; + $string =~ s/\\//g; + $string =~ s/_//g; + $string =~ s/{//g; + $string =~ s/}//g; + $string =~ s/~//g; + $string; +} + + =head2 Formatting macros beginproblem() # generates text listing number and the point value of @@ -2234,7 +2375,7 @@ sub beginproblem { (defined($effectivePermissionLevel) && defined($PRINT_FILE_NAMES_PERMISSION_LEVEL) && $effectivePermissionLevel >= $PRINT_FILE_NAMES_PERMISSION_LEVEL) || ( defined($inlist{ $studentLogin }) and ( $inlist{ $studentLogin }>0 ) )?1:0 ; $out .= MODES( TeX => - "\n%%% BEGIN PROBLEM PREAMBLE\n", #Marker used in MathBook XML extraction; contact alex.jordan@pcc.edu before modifying + "\n%%% BEGIN PROBLEM PREAMBLE\n", #Marker used in PreTeXt LaTeX extraction; contact alex.jordan@pcc.edu before modifying HTML => '

    '); if ( $print_path_name_flag ) { $out .= &M3("{\\bf ${probNum}. {\\footnotesize ($problemValue $points) \\path|$fileName|}}\\newline ", @@ -2249,8 +2390,9 @@ sub beginproblem { } $out .= MODES(%{main::PG_restricted_eval(q!$main::problemPreamble!)}); $out .= MODES( TeX => - "\n%%% END PROBLEM PREAMBLE\n", #Marker used in MathBook XML extraction; contact alex.jordan@pcc.edu before modifying + "\n%%% END PROBLEM PREAMBLE\n", #Marker used in PreTeXt LaTeX extraction; contact alex.jordan@pcc.edu before modifying HTML => ""); + if ($displayMode eq 'PTX') {$out = ''}; $out; } @@ -2312,12 +2454,12 @@ sub OL { my $i = 0; my @alpha = ('A'..'Z', 'AA'..'ZZ'); my $letter; - my $out= &M3( - "\\begin{enumerate}\n", - " \\begin{rawhtml}

      \\end{rawhtml} ", + my $out = MODES(TeX=> "\\begin{enumerate}\n", + Latex2HTML=> " \\begin{rawhtml}
        \\end{rawhtml} ", # kludge to fix IE/CSS problem #"
          \n" - "
          \n" + HTML=> "
          \n", + PTX=> '
            '."\n", ) ; my $elem; foreach $elem (@array) { @@ -2328,15 +2470,17 @@ sub OL { #HTML=> "
          1. $elem\n", HTML=> "
            $letter. $elem\n", #HTML_dpng=> "
          2. $elem

            \n" - HTML_dpng=> "
            $letter. $elem \n" + HTML_dpng=> "
            $letter. $elem \n", + PTX=> "
          3. $elem

          4. \n", ); $i++; } - $out .= &M3( - "\\end{enumerate}\n", - " \\begin{rawhtml}
          \n \\end{rawhtml} ", + $out .= MODES( + TeX=> "\\end{enumerate}\n", + Latex2HTML=> " \\begin{rawhtml}
        \n \\end{rawhtml} ", #"
      \n" - "\n" + HTML=> "\n", + PTX=> '
    '."\n", ) ; } @@ -2344,10 +2488,13 @@ sub htmlLink { my $url = shift; my $text = shift; my $options = shift; + my $sanitized_url = $url; + $sanitized_url =~ s/&/&/g; $options = "" unless defined($options); return "$BBOLD [ the link to '$text' is broken ] $EBOLD" unless defined($url) and $url; MODES( TeX => "{\\bf \\underline{$text}}", - HTML => "$text" + HTML => "$text", + PTX => "$text", ); } @@ -2389,7 +2536,8 @@ sub knowlLink { # an new syntax for knowlLink that facilitates a local HERE docu } #my $option_string = qq!url = "$options{url}" value = "$options{value}" !; MODES( TeX => "{\\bf \\underline{$display_text}}", - HTML => "$display_text" + HTML => "$display_text", + PTX => ''.$display_text.'', ); @@ -2405,6 +2553,7 @@ sub iframe { HTML => qq!\n \n!, + PTX => '', ); } @@ -2508,6 +2657,7 @@ sub appletLink { Latex2HTML => "\\begin{rawhtml} $options \\end{rawhtml}", HTML => " \n $options \n ", #HTML => qq! $options ! + PTX => 'PreTeXt does not support appletLink', ); } @@ -2517,7 +2667,8 @@ sub oldAppletLink { $options = "" unless defined($options); MODES( TeX => "{\\bf \\underline{APPLET} }", Latex2HTML => "\\begin{rawhtml} $options \\end{rawhtml}", - HTML => " $options " + HTML => " $options ", + PTX => 'PreTeXt does not support appletLink', ); } sub spf { @@ -2600,6 +2751,9 @@ sub begintable { if ($displayMode eq 'TeX') { $out .= "\n\\par\\smallskip\\begin{center}\\begin{tabular}{" . "|c" x $number . "|} \\hline\n"; } + elsif ($displayMode eq 'PTX') { + $out .= "\n".''."\n"; + } elsif ($displayMode eq 'Latex2HTML') { $out .= "\n\\begin{rawhtml} \n\\end{rawhtml}"; } @@ -2624,6 +2778,9 @@ sub endtable { if ($displayMode eq 'TeX') { $out .= "\n\\end {tabular}\\end{center}\\par\\smallskip\n"; } + elsif ($displayMode eq 'PTX') { + $out .= "\n".''."\n"; + } elsif ($displayMode eq 'Latex2HTML') { $out .= "\n\\begin{rawhtml}
    \n\\end{rawhtml}"; } @@ -2655,6 +2812,13 @@ sub row { $out .= "\\\\ \\hline \n"; # carriage returns must be added manually for tex } + elsif ($displayMode eq 'PTX') { + $out .= ''."\n"; + while (@elements) { + $out .= ''.shift(@elements).''."\n"; + } + $out .= ''."\n"; + } elsif ($displayMode eq 'Latex2HTML') { $out .= "\n\\begin{rawhtml}\n\n\\end{rawhtml}\n"; while (@elements) { @@ -2778,7 +2942,10 @@ sub image { ! - } else { + } elsif ($displayMode eq 'PTX') { + my $ptxwidth = 100*$width/600; + $out = qq!\n\n<\/sidebyside>! + } else { $out = "Error: PGbasicmacros: image: Unknown displayMode: $displayMode.\n"; } push(@output_list, $out); @@ -2796,7 +2963,8 @@ sub embedSVG { } return MODES( HTML => q! !, - TeX => "Can't process svg in tex mode yet \\includegraphics[width=6in]{" . alias( $file_name ) . "}" + TeX => "Can't process svg in tex mode yet \\includegraphics[width=6in]{" . alias( $file_name ) . "}", + PTX => '', ); } @@ -2809,7 +2977,8 @@ sub embedPDF { q! type="application/pdf" width="100%" height="100%">!, - TeX => "\\includegraphics[width=6in]{" . alias( $file_name ) . "}" + TeX => "\\includegraphics[width=6in]{" . alias( $file_name ) . "}", + PTX => '', ) ; } @@ -2880,6 +3049,9 @@ sub video { ${htmlmessage}\n \n ! + } elsif ($displayMode eq 'PTX') { + my $ptxwidth = 100*$width/600; + $out = qq!! } else { $out = "Error: PGbasicmacros: video: Unknown displayMode: $displayMode.\n"; } diff --git a/macros/PGchoicemacros.pl b/macros/PGchoicemacros.pl index 80e911e846..997d9837f6 100644 --- a/macros/PGchoicemacros.pl +++ b/macros/PGchoicemacros.pl @@ -296,6 +296,10 @@ sub std_print_q { $i++; } $out .= "\\end{enumerate}\n"; + } elsif ($main::displayMode eq 'PTX') { + $out = "
      \n
    1. "; + $out .= join("
    2. \n
    3. ",@questions); + $out .= "
    4. \n
    "; } else { $out = "Error: PGchoicemacros: std_print_q: Unknown displayMode: $main::displayMode.\n"; } @@ -356,6 +360,10 @@ sub pop_up_list_print_q { $i++; } $out .= "\\end{enumerate}\n"; + } elsif ($main::displayMode eq 'PTX') { + $out = "
      \n
    1. "; + $out .= join("
    2. \n
    3. ",@questions); + $out .= "
    4. \n
    "; } else { $out = "Error: PGchoicemacros: pop_up_list_print_q: Unknown displayMode: $main::displayMode.\n"; } @@ -417,8 +425,12 @@ sub quest_first_pop_up_list_print_q { $i++; } $out .= "\\end{enumerate}\n"; + } elsif ($main::displayMode eq 'PTX') { + $out = "
      \n
    1. "; + $out .= join("
    2. \n
    3. ",@questions); + $out .= "
    4. \n
    "; } else { - $out = "Error: PGchoicemacros: pop_up_list_print_q: Unknown displayMode: $main::displayMode.\n"; + $out = "Error: PGchoicemacros: quest_first_pop_up_list_print_q : Unknown displayMode: $main::displayMode.\n"; } $out; @@ -477,8 +489,12 @@ sub ans_in_middle_pop_up_list_print_q { $i++; } $out .= "\\end{enumerate}\n"; + } elsif ($main::displayMode eq 'PTX') { + $out = "
      \n
    1. "; + $out .= join("
    2. \n
    3. ",@questions); + $out .= "
    4. \n
    "; } else { - $out = "Error: PGchoicemacros: pop_up_list_print_q: Unknown displayMode: $main::displayMode.\n"; + $out = "Error: PGchoicemacros: ans_in_middle_pop_up_list_print_q: Unknown displayMode: $main::displayMode.\n"; } $out; @@ -537,29 +553,32 @@ sub std_print_a { my $i = 0; my @alpha = ('A'..'Z', 'AA'..'ZZ'); my $letter; - my $out= &main::M3( - "\\begin{enumerate}\n", - " \\begin{rawhtml}
      \\end{rawhtml} ", + my $out= &main::MODES( + TeX=> "\\begin{enumerate}\n", + Latex2HTML=> " \\begin{rawhtml}
        \\end{rawhtml} ", # kludge to fix IE/CSS problem #"
          \n" - "
          \n" + HTML=> "
          \n", + PTX=> '
            '."\n", ) ; my $elem; foreach $elem (@array) { $letter = shift @alpha; - $out .= &main::M3( - "\\item[$main::ALPHABET[$i].] $elem\n", - " \\begin{rawhtml}
          1. \\end{rawhtml} $elem ", + $out .= &main::MODES( + TeX=> "\\item[$main::ALPHABET[$i].] $elem\n", + Latex2HTML=> " \\begin{rawhtml}
          2. \\end{rawhtml} $elem ", #"
          3. $elem
          4. \n" - "
            $letter. $elem\n" + HTML=>"
            $letter. $elem\n", + PTX=>"
          5. $elem
          6. \n", ) ; $i++; } - $out .= &main::M3( - "\\end{enumerate}\n", - " \\begin{rawhtml}
          \n \\end{rawhtml} ", + $out .= &main::MODES( + TeX=> "\\end{enumerate}\n", + Latex2HTML=>" \\begin{rawhtml}
        \n \\end{rawhtml} ", #"
      \n" - "\n" + HTML=> "\n", + PTX=> "
    ", ) ; $out; @@ -617,6 +636,10 @@ sub radio_print_a { #$out = "\n\\par\\begin{itemize}\n"; $out .= join '', @radio_buttons; #$out .= "\\end{itemize}\n"; + } elsif ($main::displayMode eq 'PTX') { + $out = ''."\n".'
  • '; + $out .= join("
  • \n
  • ", @answers); + $out .= "
  • \n
    \n"; } else { $out = "Error: PGchoicemacros: radio_print_a: Unknown displayMode: $main::displayMode.\n"; } @@ -675,6 +698,10 @@ sub checkbox_print_a { #$out = "\n\\par\\begin{itemize}\n"; $out .= join '', @radio_buttons ; #$out .= "\\end{itemize}\n"; + } elsif ($main::displayMode eq 'PTX') { + $out = ''."\n".'
  • '; + $out .= join("
  • \n
  • ", @answers); + $out .= "
  • \n
    \n"; } else { $out = "Error: PGchoicemacros: checkbox_print_a: Unknown displayMode: $main::displayMode.\n"; } diff --git a/macros/PGessaymacros.pl b/macros/PGessaymacros.pl index 2483172603..200914ac99 100644 --- a/macros/PGessaymacros.pl +++ b/macros/PGessaymacros.pl @@ -112,11 +112,12 @@ sub essay_cmp { my $out = MODES( TeX => qq!\\vskip $height in \\hrulefill\\quad !, Latex2HTML => qq!\\begin{rawhtml}\\end{rawhtml}!, - HTML => qq! + HTML => qq! - ! + !, + PTX => '', ); $out; @@ -127,14 +128,15 @@ sub essay_cmp { my $out = MODES( TeX => '', Latex2HTML => '', - HTML => qq! + HTML => qq!

    This is an essay answer text box. You can type your answer in here and, after you hit submit, it will be saved so that your instructor can grade it at a later date. If your instructor makes any comments on your answer those comments will appear on this page after the question has been graded. You can use LaTeX to make your math equations look pretty. LaTeX expressions should be enclosed using the parenthesis notation and not dollar signs.

    - ! + !, + PTX => '', ); $out; diff --git a/macros/PGmatrixmacros.pl b/macros/PGmatrixmacros.pl index 070646c63a..aba08f2370 100644 --- a/macros/PGmatrixmacros.pl +++ b/macros/PGmatrixmacros.pl @@ -291,6 +291,9 @@ sub dm_begin_matrix { or $main::displayMode eq 'HTML_img') { $out .= qq!\n!; } + elsif ( $main::displayMode eq 'PTX' ) { + $out .= qq!\n\n!; + } else { $out = "Error: dm_begin_matrix: Unknown displayMode: $main::displayMode.\n"; } @@ -339,6 +342,8 @@ sub dm_special_tops { $out .= "$brh$erh"; } $out .= ""; + } + elsif ( $main::displayMode eq 'PTX' ) { } else { $out = "Error: dm_begin_matrix: Unknown displayMode: $main::displayMode.\n"; } @@ -348,7 +353,7 @@ sub dm_special_tops { sub dm_mat_left { my $numrows = shift; my %opts = @_; - if ($main::displayMode eq 'TeX' or $opts{'force_tex'}) { + if ($main::displayMode eq 'TeX' or $opts{'force_tex'} or $main::displayMode eq 'PTX') { return ""; # left delim is built into begin matrix } my $out=''; @@ -392,7 +397,7 @@ sub dm_mat_right { } - if ($main::displayMode eq 'TeX' or $opts{'force_tex'}) { + if ($main::displayMode eq 'TeX' or $opts{'force_tex'} or $main::displayMode eq 'PTX') { return ""; } @@ -442,6 +447,9 @@ sub dm_end_matrix { or $main::displayMode eq 'HTML_img') { $out .= "
    $erh". ' \('.$j.'\)'."$brh
    \n"; } + elsif ( $main::displayMode eq 'PTX') { + $out .= qq!\n\n!; + } else { $out = "Error: PGmatrixmacros: dm_end_matrix: Unknown displayMode: $main::displayMode.\n"; } @@ -596,6 +604,16 @@ sub dm_mat_row { } if(not $opts{'isfirst'}) {$out .="$brh$erh\n";} } + elsif ($main::displayMode eq 'PTX') { + $out .= "\n"; + while (@elements) { + $colcount++; + $out .= ''; + $out .= shift(@elements); + $out .= "\n"; + } + $out .= "\n"; + } else { $out = "Error: dm_mat_row: Unknown displayMode: $main::displayMode.\n"; } diff --git a/macros/alignedChoice.pl b/macros/alignedChoice.pl index fe0af9ed3b..b885e5150f 100644 --- a/macros/alignedChoice.pl +++ b/macros/alignedChoice.pl @@ -62,6 +62,17 @@ sub aligned_print_q { $out .= "\\ ". ans_rule($length) . $rest . "\\\\ \\noalign{\\kern $tsep}\n"; } $out .= "\\end{tabular}\n"; + } elsif ($main::displayMode eq "PTX") { + $out = "\n\n"; + foreach $quest (@questions) { + if (ref($quest) eq 'ARRAY') {($quest,$rest) = @{$quest}} else {$rest = ''} + $out .= "\n"; + $out .= "" . $i++ . ".\n" if ($numbered); + $out .= "$quest\n"; + $out .= "=\n" if ($equals); + $out .= "" . ans_rule($length) . $rest . "\n\n"; + } + $out .= "\n\n"; } else { $out = "Error: std_aligned_print_q: Unknown displayMode: ". $main::displayMode; diff --git a/macros/compoundProblem.pl b/macros/compoundProblem.pl index 26bd3ef1e4..51bf1f818c 100644 --- a/macros/compoundProblem.pl +++ b/macros/compoundProblem.pl @@ -520,7 +520,7 @@ sub nextForced { sub part { my $self = shift; my $status = $self->{status}; my $part = shift; - return $status->{part} unless defined $part && $main::displayMode ne 'TeX'; + return $status->{part} unless defined $part && $main::displayMode ne 'TeX' && $main::displayMode ne 'PTX'; $part = 1 if $part < 1; $part = $self->{parts} if $part > $self->{parts}; if ($part > $status->{part} && !$main::inputs_ref->{_noadvance}) { unless ((lc($self->{nextVisible}) eq 'ifcorrect' && $status->{raw} < 1) || diff --git a/macros/compoundProblem2.pl b/macros/compoundProblem2.pl index 810bd917c9..8718b07e92 100644 --- a/macros/compoundProblem2.pl +++ b/macros/compoundProblem2.pl @@ -101,10 +101,11 @@ sub DISPLAY_SECTION {

    Part: $name:

    - !, TeX=>"\\par{\\bf Part: $name }\\par")); + !, TeX=>"\\par{\\bf Part: $name }\\par", + PTX=>"\n")); my $rendered_text_string = EV3($text_string); TEXT( $rendered_text_string ) if $options{canshow}==1; - TEXT( MODES(HTML=>"

    ", TeX=>'\\par' ) ); + TEXT( MODES(HTML=>"

    ", TeX=>'\\par', PTX=>"\n" ) ); } @@ -112,16 +113,16 @@ sub DISPLAY_SECTION { # FIXME we will make a $cp object that keeps track of the part -sub BEGIN_SECTIONS {TEXT(MODES(HTML=>q!
      !,TeX=>'')); warn "start sections\n\n"; } +sub BEGIN_SECTIONS {TEXT(MODES(HTML=>q!
        !,TeX=>'',PTX=>'')); warn "start sections\n\n"; } sub END_SECTIONS { my $part = shift; - TEXT(MODES( HTML=>q!
      '')); + TEXT(MODES( HTML=>q!
    '',PTX=>'')); TEXT(MODES(HTML =>$PAR .qq! - ! , TeX=>'')); + ! , TeX=>'',PTX=>'')); } -1; \ No newline at end of file +1; diff --git a/macros/compoundProblem5.pl b/macros/compoundProblem5.pl index 348383f940..7393773e52 100644 --- a/macros/compoundProblem5.pl +++ b/macros/compoundProblem5.pl @@ -441,7 +441,7 @@ sub process_section { # Get the script to open or prevent the section from opening # my $action = $canshow ? "canshow()" : "cannotshow()"; - my $scriptpreamble = main::MODES(TeX=>'', HTML=>qq!!); + my $scriptpreamble = main::MODES(TeX=>'', PTX=>'', HTML=>qq!!); my $renderedtext = $canshow ? $section->{renderedtext} : '' ; $renderedtext = $scriptpreamble . "\n" . $renderedtext; $renderedtext .= $section->{solution} if main::not_null($section->{solution}); @@ -453,7 +453,8 @@ sub process_section { HTML=> qq!
  • Section: $name:

    $renderedtext

  • - !, TeX=>"\\par{\\bf Section: $name}\\par $renderedtext\\par" + !, TeX=>"\\par{\\bf Section: $name}\\par $renderedtext\\par", + PTX=>"\n$renderedtext\n", ); ($iscorrect,$canshow); } @@ -675,7 +676,7 @@ sub openSections { my $self = shift; my $script = ''; $self->HIDE_OTHER_RESULTS(@_); foreach my $s (@_) {$script .= qq!\$("#section$s").openSection()\n!;} - main::TEXT(main::MODES(TeX=>'', HTML=>qq!!)); + main::TEXT(main::MODES(TeX=>'', PTX=>'', HTML=>qq!!)); } diff --git a/macros/contextArbitraryString.pl b/macros/contextArbitraryString.pl index e6aacad5ff..ddfb34bf4b 100644 --- a/macros/contextArbitraryString.pl +++ b/macros/contextArbitraryString.pl @@ -112,7 +112,7 @@ sub quoteHTML { my $self = shift; my $s = $self->SUPER::quoteHTML(shift); $s = "
    $s
    " - unless $main::displayMode eq "TeX"; + unless ($main::displayMode eq "TeX" or $main::displayMode eq "PTX"); return $s; } diff --git a/macros/niceTables.pl b/macros/niceTables.pl index b2ea8bec37..a3be77d384 100644 --- a/macros/niceTables.pl +++ b/macros/niceTables.pl @@ -36,6 +36,11 @@ =head2 pccTables.pl # Generally, you give settings for the hard copy tex version first. Many common such settings are automatically # translated into CSS styling for the on-screen. You can then override or augment the CSS for the on-screen version. # + # With PTX output, not all features below are supported. Perhaps they can be added upon request. + # Contact Alex Jordan with questions. + # This version supports the center, caption, midrules, encase, and noencase options in PTX. It also honors the + # horizontal alignment portions of the align option (but not vertical rules or anything found in @{}). + # # Options for the WHOLE TABLE # # Applies to on-screen *and* hard copy: @@ -324,7 +329,9 @@ sub DataTable { # alignment: p{width}, r, c, l, or X my @alignmentcolumns; for my $i (0..$#columnalignments) {$alignmentcolumns[$columnalignments[$i]] = $i}; - # @alignmentcolumns is an array with one element per column, where the elements are each one of p{width}, r, c, l, or X + # @alignmentcolumns is an array whose ith element is undefined unless the ith element of @htmlalignment was one + # of p{width}, r, c, l, or X. Otherwise it is the index of the entry in @columnalignments that corresponds to + # that alignment # append css to author's columnscss->[$i] that corresponds to the alignemnts in @alignmentcolumns for my $i (0..$#columnalignments) { @@ -441,7 +448,9 @@ sub DataTable { my @alignmentcolumns; for my $k (0..$#columnalignments) {$alignmentcolumns[$columnalignments[$k]] = $k}; - # @alignmentcolumns is an array with one element per column, where the elements are each one of p{width}, r, c, l, or X + # @alignmentcolumns is an array whose ith element is undefined unless the ith element of @htmlalignment was one + # of p{width}, r, c, l, or X. Otherwise it is the index of the entry in @columnalignments that corresponds to + # that alignment # Again, this should only have one entry. for my $k (0..$#columnalignments) { @@ -494,15 +503,23 @@ sub DataTable { if ($midrules == 1) {$midrulescss = 'border-top:solid 1px; '}; my $table = ''; - # build html string for the table + my $ptxtable = ''; + # build html and ptx strings for the table (which have structural similarities that distinguish them from tex) if ($options{LaYoUt} != 1) { $table = ''; - if ($caption ne '') {$table .= '';} + $ptxtable = "\n\n"; $table .= ''; for my $i (0..$#{$columnscss}) {$columnscss->[$i] = '' unless (defined($columnscss->[$i])); - $table .= '';}; + $table .= ''; + }; $table .= ''; + if ($caption ne '') { + $table .= ''; + # Needs to be a better way to incorporate the caption into PTX output + # This way makes "captions" that extend past the table + #$ptxtable .= "\n".''.$caption.''."\n\n"; + } my $bodystarted = 0; for my $i (0..$#{$dataref}) {my $midrulecss = ($midrule[$i] == 1) ? 'border-bottom:solid 1px; ' : ''; @@ -510,6 +527,7 @@ sub DataTable { if ($headerrow[$i] == 1) {$table .= ''; } elsif (!$bodystarted) {$table .= ''; $bodystarted = 1}; $table .= ''; + $ptxtable .= "\n"; for my $j (0..$numcols[$i]) {my $colspan = (${$dataref->[$i][$j]}{colspan} eq '') ? '' : 'colspan = "'.${$dataref->[$i][$j]}{colspan}.'" '; if (uc(${$dataref->[$i][$j]}{header}) eq 'TH') @@ -523,12 +541,15 @@ sub DataTable { elsif (uc($headerrow[$i]) == 1) {$table .= '';} else {$table .= '';} + $ptxtable .= '' . ${$dataref->[$i][$j]}{data} . '' . "\n"; } $table .= ""; + $ptxtable .= "\n"; if ($headerrow[$i] == 1) {$table .= '';} elsif ($bodystarted and ($i == $#{$dataref})) {$table .= '';}; }; $table .= "
    '.$caption.''.$caption.'
    '.${$dataref->[$i][$j]}{data}.''.${$dataref->[$i][$j]}{data}.'
    "; + $ptxtable .= "\n"; }# now if it is a Layout Table... else { $table = '
    '; @@ -616,6 +637,7 @@ sub DataTable { MODES( TeX => $textable, HTML => $table, + PTX => $ptxtable, ); } @@ -663,6 +685,4 @@ sub TeX_Alignment_to_CSS { return $css; } - - 1; diff --git a/macros/parserPopUp.pl b/macros/parserPopUp.pl index c2e7621904..f534c677be 100644 --- a/macros/parserPopUp.pl +++ b/macros/parserPopUp.pl @@ -172,6 +172,14 @@ sub MENU { $menu .= qq!$option\n!; }; $menu .= ""; + } elsif ($main::displayMode eq 'PTX') { + $menu = '' . "\n"; + foreach my $item (@list) { + $menu .= '
  • '; + my $cleaned_item = main::PTX_special_character_cleanup($item); + $menu .= $cleaned_item . '
  • '. "\n"; + } + $menu .= '
    '; } elsif ($main::displayMode eq "TeX") { # if the total number of characters is not more than # 30 and not containing / or ] then we print out diff --git a/macros/parserRadioButtons.pl b/macros/parserRadioButtons.pl index dc13f18103..d0170a6b5a 100644 --- a/macros/parserRadioButtons.pl +++ b/macros/parserRadioButtons.pl @@ -556,8 +556,12 @@ sub BUTTONS { $radio[0] = "\n\\begin{itemize}\n" . $radio[0]; $radio[$#radio_buttons] .= "\n\\end{itemize}\n"; } + if ($main::displayMode eq 'PTX') { + $radio[0] = '' . "\n" . $radio[0]; + $radio[$#radio_buttons] .= ''; + }; @radio = $self->makeUncheckable(@radio) if $self->{uncheckable}; - (wantarray) ? @radio : join($self->{separator}, @radio); + (wantarray) ? @radio : join(($main::displayMode eq 'PTX')?'':$self->{separator}, @radio); } sub protect { diff --git a/macros/scaffold.pl b/macros/scaffold.pl index 0327763d57..5e32c20f33 100644 --- a/macros/scaffold.pl +++ b/macros/scaffold.pl @@ -275,6 +275,7 @@ package Scaffold; $isLibrary || $main::envir{effectivePermissionLevel} >= $main::envir{ALWAYS_SHOW_SOLUTION_PERMISSION_LEVEL} ); our $isHardcopy = ($main::displayMode eq "TeX"); +our $isPTX = ($main::displayMode eq "PTX"); our $afterAnswerDate = (time() > $main::envir{answerDate}); our $scaffold; # the active scaffold (set by Begin() below) @@ -430,7 +431,7 @@ sub is_open { sub open_sections { my $self = shift; my @script = map {'$("#'.$self->{sections}{$_}{label}.'").opensection();'} @_; - push(@{$self->{output}},main::MODES(TeX=>'', HTML=>"")); + push(@{$self->{output}},main::MODES(TeX=>'', HTML=>"",PTX=>'')); $self->hide_other_results(@_); } @@ -571,7 +572,7 @@ sub add_container { $isopen = $self->is_open; $scaffold->is_open($self) if $isopen; - splice(@$PG_OUTPUT,0,scalar(@$PG_OUTPUT)) if !($canopen || $iscorrect) || (!$isopen && $Scaffold::isHardcopy); + splice(@$PG_OUTPUT,0,scalar(@$PG_OUTPUT)) if !($canopen || $iscorrect || $Scaffold::isPTX) || (!$isopen && $Scaffold::isHardcopy); unshift(@$PG_OUTPUT,@{main::MODES( HTML => [ '
    ', @@ -580,10 +581,12 @@ sub add_container { '', ], TeX => ["\\par{\\bf $self->{name}}\\par "], + PTX => ["\n"], )}); push(@$PG_OUTPUT,main::MODES( HTML => '

    ', - TeX => "\\par " + TeX => "\\par ", + PTX => "<\/stage>\n", )); } @@ -745,7 +748,7 @@ package main; # # Set up some styles and the jQuery calls for opening and closing the scaffolds. # -TEXT(<<'END_HEADER_TEXT') if !$Scaffold::isHardcopy; # should be HEADER_TEXT, but that gets lost in library browser +TEXT(<<'END_HEADER_TEXT') if !($Scaffold::isHardcopy or $Scaffold::isPTX); # should be HEADER_TEXT, but that gets lost in library browser "; $state->{state_summary_msg} = - "Note: This is a new (re-randomized) version of the problem.".$main::BR. - "If you come back to it later, it may revert to its original version.".$main::BR. - "Hardcopy will always print the original version of the problem."; + "".$main::PG->maketext("Note:").""." ".$main::PG->maketext("This is a new (re-randomized) version of the problem.").$main::BR. + $main::PG->maketext("If you come back to it later, it may revert to its original version.").$main::BR. + $main::PG->maketext("Hardcopy will always print the original version of the problem."); } # From d5dfcdddd262aed809d8a819ba7c3b2cd95d467c Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Wed, 2 May 2018 21:44:39 -0700 Subject: [PATCH 52/70] In PTX output, merge consecutive sidebysides into one --- macros/PGbasicmacros.pl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/macros/PGbasicmacros.pl b/macros/PGbasicmacros.pl index 36f81f9371..1c6fc18377 100644 --- a/macros/PGbasicmacros.pl +++ b/macros/PGbasicmacros.pl @@ -2247,6 +2247,9 @@ sub PTX_cleanup { $string =~ s/(?s)((?:\s|\n(.*?)<\/sidebyside>\n\n/\n\2/g; + }; $string; } From 7996ae5a1757e42449a45c15c395db0b7d1fb693 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Wed, 2 May 2018 21:56:30 -0700 Subject: [PATCH 53/70] implement PGML explicit disc --- macros/PGML.pl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/macros/PGML.pl b/macros/PGML.pl index fe0ee332d9..00fd10a6ab 100644 --- a/macros/PGML.pl +++ b/macros/PGML.pl @@ -357,7 +357,7 @@ sub Rule { sub Bullet { my $self = shift; my $token = shift; my $bullet = shift; return $self->Text($token) unless $self->{atLineStart}; - $bullet = {'*'=>'bullet', '+'=>'square', 'o'=>'circle', '-'=>'bullet'}->{substr($token,0,1)} if $bullet eq 'bullet'; + $bullet = {'*'=>'disc', '+'=>'square', 'o'=>'circle', '-'=>'bullet'}->{substr($token,0,1)} if $bullet eq 'bullet'; my $block = $self->{block}; if ($block->{type} ne 'root' && !$block->{align}) { while ($block->{type} ne 'root' && !$block->{prev}{align}) {$block = $block->{prev}} @@ -1094,6 +1094,7 @@ sub Align { Alpha => 'ol type="A"', roman => 'ol type="i"', Roman => 'ol type="I"', + disc => 'ul type="disc"', circle => 'ul type="circle"', square => 'ul type="square"', ); @@ -1531,6 +1532,7 @@ sub LaTeX { \def\pgmlIndent{\par\advance\leftskip by 2em \advance\pgmlPercent by .02em \pgmlCount=0}% \def\pgmlbulletItem{\par\indent\llap{$\bullet$ }\ignorespaces}% +\def\pgmldiscItem{\par\indent\llap{$\bullet$ }\ignorespaces}% \def\pgmlcircleItem{\par\indent\llap{$\circ$ }\ignorespaces}% \def\pgmlsquareItem{\par\indent\llap{\vrule height 1ex width .75ex depth -.25ex\ }\ignorespaces}% \def\pgmlnumericItem{\par\indent\advance\pgmlCount by 1 \llap{\the\pgmlCount. }\ignorespaces}% From 483753da340f2d9dabfca3d45fee0d865b4cbeec Mon Sep 17 00:00:00 2001 From: Doug Torrance Date: Tue, 22 May 2018 18:40:03 -0400 Subject: [PATCH 54/70] Fix typo in radio button error message. --- macros/parserRadioButtons.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macros/parserRadioButtons.pl b/macros/parserRadioButtons.pl index d0170a6b5a..33f5f6d53f 100644 --- a/macros/parserRadioButtons.pl +++ b/macros/parserRadioButtons.pl @@ -318,7 +318,7 @@ sub getCorrectChoice { my $self = shift; my $value = shift; if ($value =~ m/^\d+$/ && !$self->{noindex}) { $value = ($self->flattenChoices)[$value]; - Value::Error("The correct anser index is outside the range of choices provided") + Value::Error("The correct answer index is outside the range of choices provided") if !defined($value); } my @choices = @{$self->{orderedChoices}}; From 2802880666a18e134be22445c325d0a3dd05dfef Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Tue, 29 May 2018 15:50:36 -0700 Subject: [PATCH 55/70] PGML true display math --- macros/PGML.pl | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/macros/PGML.pl b/macros/PGML.pl index 00fd10a6ab..ce28d5f7fd 100644 --- a/macros/PGML.pl +++ b/macros/PGML.pl @@ -44,8 +44,8 @@ package PGML::Parse; my $emphasis = '\*+|_+'; my $chars = '\\\\.|[{}[\]\'"]'; my $ansrule = '\[(?:_+|[ox^])\]\*?'; -my $open = '\[(?:[!<%@$]|::?|``?|\|+ ?)'; -my $close = '(?:[!>%@$]|::?|``?| ?\|+)\]'; +my $open = '\[(?:[!<%@$]|::?:?|``?`?|\|+ ?)'; +my $close = '(?:[!>%@$]|::?:?|``?`?| ?\|+)\]'; my $noop = '\[\]'; my $splitPattern = @@ -454,11 +454,16 @@ sub NOOP { parsed=>1, allowStar=>1, allowDblStar=>1, allowTriStar=>1, options=>["context","reduced"]}, "[::" => {type=>'math', parseComments=>1, parseSubstitutions=>1, terminator=>qr/::\]/, terminateMethod=>'terminateGetString', + parsed=>1, allowStar=>1, allowDblStar=>1, allowTriStar=>1, displaystyle=>1, options=>["context","reduced"]}, + "[:::" => {type=>'math', parseComments=>1, parseSubstitutions=>1, + terminator=>qr/:::\]/, terminateMethod=>'terminateGetString', parsed=>1, allowStar=>1, allowDblStar=>1, allowTriStar=>1, display=>1, options=>["context","reduced"]}, "[`" => {type=>'math', parseComments=>1, parseSubstitutions=>1, terminator=>qr/\`\]/, terminateMethod=>'terminateGetString',}, "[``" => {type=>'math', parseComments=>1, parseSubstitutions=>1, - terminator=>qr/\`\`\]/, terminateMethod=>'terminateGetString', display=>1}, + terminator=>qr/\`\`\]/, terminateMethod=>'terminateGetString', displaystyle=>1}, + "[```" => {type=>'math', parseComments=>1, parseSubstitutions=>1, + terminator=>qr/\`\`\`\]/, terminateMethod=>'terminateGetString', display=>1}, "[!" => {type=>'image', parseComments=>1, parseSubstitutions=>1, terminator=>qr/!\]/, terminateMethod=>'terminateGetString', cancelNL=>1, options=>["title"]}, @@ -966,8 +971,9 @@ sub Math { $obj = $obj->reduce if $item->{reduced}; $math = $obj->TeX; } - $math = "\\displaystyle{$math}" if $item->{display}; - return $math; + $math = "\\displaystyle{$math}" if $item->{displaystyle}; + my $mathmode = ($item->{display}) ? 'display' : 'inline' ; + return ($math,$mathmode); } sub Answer { @@ -1188,7 +1194,7 @@ sub Verbatim { sub Math { my $self = shift; - return main::math_ev3($self->SUPER::Math(@_)); + return main::general_math_ev3($self->SUPER::Math(@_)); } ###################################################################### @@ -1328,9 +1334,10 @@ sub Verbatim { return $text; } + sub Math { my $self = shift; - return main::math_ev3($self->SUPER::Math(@_)); + return main::general_math_ev3($self->SUPER::Math(@_)); } ###################################################################### @@ -1456,7 +1463,7 @@ sub Verbatim { sub Math { my $self = shift; - return main::math_ev3($self->SUPER::Math(@_)); + return main::general_math_ev3($self->SUPER::Math(@_)); } From 1869a4f836e3f3e16885deb0a103e5314f4dd69b Mon Sep 17 00:00:00 2001 From: Michael Gage Date: Fri, 1 Jun 2018 13:37:50 -0400 Subject: [PATCH 56/70] Cleaned up sage.pl for pull request according to comments 1. fixed "top_test" typo in sageCalculatorPad 2. added hide: option to hid the "share" permalink button 3. added POD documentation for top level functions 4. Added explanatory comments for obscure code, or code handling legacy conditions from older problems 5. Removed #mycell div support from sageCalculatorPad --- macros/sage.pl | 209 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 162 insertions(+), 47 deletions(-) diff --git a/macros/sage.pl b/macros/sage.pl index e48d33f4c4..1eee71da97 100644 --- a/macros/sage.pl +++ b/macros/sage.pl @@ -5,85 +5,168 @@ sub _sage_init { PG_restricted_eval('sub Sage {new sage(@_) }'); -} +} +# Sage() is defined as an alias for creating a new sage object. + + + package sage; -## Options: -## sage( SageCode (not yet), ButtonText, CellServerAddress) +=head3 Sage cell + + usage: Sage( SageCode => 'print 1+2; record_answer(3)', + ButtonText => 'Start/Restart the Interactive Cell', + ShowAnswerBlank => # "hidden" (default) or "visible" or 'none' + CellServerAddress => 'https://sagecell.sagemath.org' + ); + NAMED_ANS(sageAnswer => Compute('3')->cmp); + +The arguments are all optional but usually you will want to supply your own SageCode. + +This method of calling sage was designed specially for presenting sage "interacts", applet +like creations in SageMath, although it may be useful for other purposes also. If the answer blank +is hidden then the interact fills in the answer as a result of manipulations on the applet +performed by the student and the student cannot override the answer. + +To return answers from the sage interact: + +The function record_answer(answer_list) called from within the SageCode +creates a NAMED_ANS_RULE or NAMED_HIDDEN_ANS_RULE with +the values of the answer_list inserted. If ShowAnswerBlank is "hidden" then the HIDDEN answer rule is +used; if ShowAnswerBlank is 'none' then no answer blank is inserted. + +For the current implementation the Sage interact can create only one answer blank. + +When the sage interact creates an answer blank it must be checked by WeBWorK using the construction + +C $correctAnswer-Ecmp)> + +where 'sageAnswer' is the SageAnswerName and $correctAnswer is a MathObject. + +By default the sage created answer blanks are hidden, but it is visible if ShowAnswerBlank is set to +'visible'. When visible the answer blanks occur within the borders which define the output +of the sage applet. The answer blanks are 15 spaces long. + + + +=cut sub new { my $self = shift; my $class = ref($self) || $self; my %options = ( - SageCode => 'print 1+2', + SageCode => 'print 1+2;record_answer(3)', ButtonText => 'Start/Restart the Interactive Cell', CellServer => 'https://sagecell.sagemath.org', - SageAnswerName => 'sageAnswer', # not used yet - SageAnswerValue => 'ansList', # not used yet + SageAnswerName => 'sageAnswer', + SageAnswerValue => 'ansList', # used in early versions, may no longer be needed AutoEvaluateCell => 'true', - ShowAnswerBlank => 'hidden', #'hidden', - AnswerReturn => 1, # 0 means no answer blank is registered - accepted_tos =>'false', # force author to accept terms of service explicitly + ShowAnswerBlank => 'hidden', #'hidden','visible','none' + AnswerReturn => 1, # (legacy, use ShowAnswerBlank=>'none') + # 0 means no answer blank is registered +# accepted_tos =>'false', # force author to accept terms of service explicitly + # removed because sagecell.sagemath.org no longer requires + # acknowledgement of terms of service. @_ ); - $self = bless { - %options - }, $class; - $options{ShowAnswerBlank} = 'none' if $options{AnswerReturn} == 0; + +# handle legacy case where AnswerReturn was used + unless ($options{ShowAnswerBlank} =~ \S){ + if ($options{AnswerReturn} == 0) { + $options{ShowAnswerBlank} = 'none'; + } else { + $options{ShowAnswerBlank} = 'hidden'; + } + } + + + # lets create a new hash for "self" + $self = bless {%options}, $class; + + # main::RECORD_ANS_NAME($self->{SageAnswerName}, 345); -- old version of code - # the following options register a named answer blank - # and produce the HTML text for embedding it. + + # $recordAnswerBlank wil hold the code defining 'record_answer' which, when called from + # within Sage prints a WeBWorK (HIDDEN)_NAMED_ANS_RULE and inserts the answers values. + # This is the mechanism for returning an answer created by the sage interact. + # By default this answer blank is hidden, but it is visible if ShowAnswerBlank is set to + # 'visible'. When visible the answer blanks occur within the borders which define the output + # of the sage applet. The answer blanks are 15 spaces long. + # FIXME: For the current implementation the Sage interact can create only one answer blank. + # provisions for giving each answer blank a different label would need to be created. + my $recordAnswerBlank=''; - if ($options{ShowAnswerBlank} eq 'visible') { - $recordAnswerBlank= main::NAMED_ANS_RULE($self->{SageAnswerName},15); - } elsif ($options{ShowAnswerBlank} eq 'hidden') { - $recordAnswerBlank= main::NAMED_HIDDEN_ANS_RULE($self->{SageAnswerName},15); - } elsif ($options{ShowAnswerBlank} eq 'none') { - $recordAnswerBlank=''; # don't register an answer blank + if ($self->{ShowAnswerBlank} eq 'visible') { + $recordAnswerBlank = "Answer: ".main::NAMED_ANS_RULE($self->{SageAnswerName}, 15); + } elsif ($self->{ShowAnswerBlank} eq 'hidden') { + $recordAnswerBlank = main::NAMED_HIDDEN_ANS_RULE($self->{SageAnswerName},15); + } elsif ($self->{ShowAnswerBlank} eq 'none') { + $recordAnswerBlank = 'none'; # don't register an answer blank } else { main::WARN_MESSAGE("Option $option{ShowAnswerBlank} is not valid for displaying sage answer rule. "); } # you could add an option to print an ANSWER BOX instead of an ANSWER RULE - $sage::recordAnswerString = < + # ' + # %(ansVals,) ) ) + # the next line replaces the first VALUE="(1, 1)" with %s, so that we have: + # def record_answer(ansVals): pretty_print( HtmlFragment( + # ' + # ' + # %(ansVals,) ) ) + # When evaluated in sage (python) %s is replaced by the value of ansVals + $sage::recordAnswerString =~ s/value="[^"]*"/value="%s"/i; + # this line removes any returns from the output -- this might not be necessary $sage::recordAnswerString =~ s/\n/ /g; -# my $recordAnswerString2 = $sage::recordAnswerString; -# $recordAnswerString2 =~ s/{SageAnswerValue}): -# # pretty_print( HtmlFragment('{ShowAnswerBlank} size=15 name="$self->{SageAnswerName}" id="$self->{SageAnswerName}" value="%s">'%($self->{SageAnswerValue},) ) ) - + $self->sageCode(); $self->sagePrint(); return $self; } +# Notice that python is white space sensitive so the code +# needs to be left justified when inserted to +# avoid indentation errors. +# + sub sageCode{ my $self = shift; main::TEXT(main::MODES(TeX=>"", HTML=><<"SAGE_CODE")); - -
    @@ -110,7 +193,42 @@ sub sagePrint{ } -#### Experimental stuff. +=head3 sageCalculatorPad code. + + +This is a simple interface for embedding a sage calculation cell in any problem. +Details for this can be found at +L +and for more detail: +L. + +The latter reference provides information for embedding a customized sageCell with more +options than are provided by sageCalculatorPad() + +=cut + +=head3 Sample sageCalculatorPad + + sageCalculatorHeader(); # set up javaScript needed for the sageCalculatorPad + + + Context()->texStrings; + TEXT( + sageCalculatorPad( "Use this calculator pad to make calculations", + q! + data = [1, 3, 4, 1, 7, 4, 2, 3, 2, 4, 2, 5, 4, 1, 3, 3, 2] + n = len(data); print "Number of data values =",n + s = sum(data); print " Sum of data = ",s + s2 = sum((x^2 for x in data)); print " Sum of squares = ",s2 + s3 = sum((x^3 for x in data)); print " Sum of cubes = ",s3 + s4 = sum((x^4 for x in data)); print " Sum of forths = ",s4;print + mu = mean(data); print " The mean =",mu + var = variance(data); print " The sample variance = ",var + ! + ) + ); + +=cut package main; sub sageCalculatorHeader { @@ -119,13 +237,10 @@ sub sageCalculatorHeader { @@ -134,14 +249,14 @@ sub sageCalculatorHeader { } sub sageCalculatorPad { - my $top_test = shift; + my $top_text = shift; my $initial_contents = shift; -main::TEXT(main::MODES(TeX=>"", HTML=><<"EOF")); +main::TEXT(main::MODES(TeX=>"SageCell: $top_text", HTML=><<"EOF"));

    $top_text

    -

    +