diff --git a/LICENSE b/LICENSE
index 98bc5d4468..8df1667545 100644
--- a/LICENSE
+++ b/LICENSE
@@ -2,7 +2,7 @@
Online Homework Delivery System
Version 2.*
- Copyright 2000-2017, The WeBWorK Project
+ Copyright 2000-2018, The WeBWorK Project
All rights reserved.
This program is free software; you can redistribute it and/or modify
diff --git a/VERSION b/VERSION
index 1cb0ed472f..2650dc99cd 100644
--- a/VERSION
+++ b/VERSION
@@ -1,4 +1,4 @@
-$PG_VERSION ='develop';
-$PG_COPYRIGHT_YEARS = '1996-2017';
+$PG_VERSION ='PG-2.14';
+$PG_COPYRIGHT_YEARS = '1996-2018';
1;
diff --git a/lib/AnswerHash.pm b/lib/AnswerHash.pm
index 711c304dfa..a2dee9e85e 100755
--- a/lib/AnswerHash.pm
+++ b/lib/AnswerHash.pm
@@ -20,7 +20,7 @@
AnswerHash -- this class stores information related to the student's
answer. It is little more than a standard perl hash with
- a special name, butit does have some access and
+ a special name, but it does have some access and
manipulation methods. More of these may be added as it
becomes necessary.
diff --git a/lib/Applet.pm b/lib/Applet.pm
index 764cd090d4..80fecf5bf7 100644
--- a/lib/Applet.pm
+++ b/lib/Applet.pm
@@ -1144,14 +1144,27 @@ use constant GEOGEBRAWEB_OBJECT_HEADER_TEXT =><<'END_HEADER_SCRIPT';
END_HEADER_SCRIPT
+# Some changes in the way geogebra javaScript works make it important
+# That the object and the script that calls it are contained in some
+# (otherwise geogebra adds height and width values to the second enclosing
+# (i.e. the div enclosing the enclosing div) and
+# if the div contains more than just the geogebra applet this size will be incorrect. )
+# (This behavior is probably a bug in geogebra --
+# but I don't have a precise statement of the API.)
+# The
<<'END_OBJECT_TEXT';
+
+
$webgeogebraParameters
-
+
+
END_OBJECT_TEXT
sub new {
diff --git a/lib/Label.pm b/lib/Label.pm
index 193020427e..140b8fa57c 100644
--- a/lib/Label.pm
+++ b/lib/Label.pm
@@ -18,14 +18,18 @@ This module defines labels for the graph objects (WWPlot).
=head2 Usage
- $label1 = new Label($x_value, $y_value, $label_string, $label_color, @justification)
- $justification = one of ('left', 'center', 'right) and ('bottom', 'center', 'top')
- describes the position of the ($x_value, $y_value) within the string.
- The default is 'left', 'top'
+ $label1 = new Label($x_value, $y_value, $label_string, $label_color, @options)
+ $options is an array with (*'d defaults)
+ - one of 'left'*, 'center', 'right' (horizontal alignment)
+ - one of 'bottom', 'center', 'top'* (verical alignment)
+ - one of 'horizontal'*, 'vertical' (orientation)
+ - one of 'small', 'large', 'mediumbold'*, 'tiny', 'giant' (which gd font to use)
+ Note the alignment specifications are relative to the English reading of the string,
+ even when the orientation is vertical.
-=head2 Example
+=head2 Example:
$new_label = new Label ( 0,0, 'origin','red','left', 'top')
@labels = $graph->lb($new_label);
@@ -51,14 +55,15 @@ use strict;
@Label::ISA = qw(WWPlot);
my %fields =(
- 'x' => 0,
- 'y' => 0,
- color => 'black',
- font => GD::gdMediumBoldFont, #gdLargeFont
- # constants from GD need to be addressed fully, they have not been imported.
- str => "",
- lr_nudge => 0, #justification parameters
- tb_nudge => 0,
+ 'x' => 0,
+ 'y' => 0,
+ color => 'black',
+ font => GD::gdMediumBoldFont, #gdLargeFont
+ # constants from GD need to be addressed fully, they have not been imported.
+ str => "",
+ lr_nudge => 0, #justification parameters
+ tb_nudge => 0,
+ orientation => 'horizontal',
);
@@ -74,31 +79,46 @@ sub new {
}
sub _initialize {
- my $self = shift;
- my ($x,$y,$str,$color,@justification) = @_;
- $self -> x($x);
- $self -> y($y);
- $self -> str($str);
- $self -> color($color) if defined($color);
- my $j;
- foreach $j (@justification) {
- $self->lr_nudge( - length($self->str) ) if $j eq 'right';
- $self->tb_nudge( - 1 ) if $j eq 'bottom';
- $self->lr_nudge( - ( length($self->str) )/2)if $j eq 'center';
- $self->tb_nudge(-0.5) if $j eq 'middle';
-# print "\njustification=$j",$self->lr_nudge,$self->tb_nudge,"\n";
- }
+ my $self = shift;
+ my ($x,$y,$str,$color,@justification) = @_;
+ $self -> x($x);
+ $self -> y($y);
+ $self -> str($str);
+ $self -> color($color) if defined($color);
+ my $j;
+ foreach $j (@justification) {
+ if ($j eq 'right') {$self->lr_nudge( - length($self->str) ); }
+ elsif ($j eq 'bottom') {$self->tb_nudge( - 1 ); }
+ elsif ($j eq 'center') {$self->lr_nudge( - ( length($self->str) )/2); }
+ elsif ($j eq 'middle') {$self->tb_nudge(-0.5); }
+ elsif ($j eq 'vertical') {$self->orientation($j); }
+ #there are only five avialble fonts: http://search.cpan.org/~rurban/GD-2.68/lib/GD.pm#Font_Utilities
+ elsif ($j eq 'small') {$self->font(GD::gdSmallFont); }
+ elsif ($j eq 'large') {$self->font(GD::gdLargeFont); }
+ elsif ($j eq 'tiny') {$self->font(GD::gdTinyFont); }
+ elsif ($j eq 'giant') {$self->font(GD::gdGiantFont); }
+ }
}
sub draw {
- my $self = shift;
- my $g = shift; #the containing graph
- $g->im->string( $self->font,
- $g->ii($self->x)+int( $self->lr_nudge*($self->font->width) ),
- $g->jj($self->y)+int( $self->tb_nudge*($self->font->height) ),
- $self->str,
- ${$g->colors}{$self->color}
- );
-
+ my $self = shift;
+ my $g = shift; #the containing graph
+ if ($self->orientation eq 'horizontal') {
+ $g->im->string( $self->font,
+ $g->ii($self->x)+int( $self->lr_nudge*($self->font->width) ),
+ $g->jj($self->y)+int( $self->tb_nudge*($self->font->height) ),
+ $self->str,
+ ${$g->colors}{$self->color}
+ );
+ }
+ elsif ($self->orientation eq 'vertical') {
+ $g->im->stringUp( $self->font,
+ $g->ii($self->x)+int( $self->tb_nudge*($self->font->height) ),
+ $g->jj($self->y)-int( $self->lr_nudge*($self->font->width) ),
+ $self->str,
+ ${$g->colors}{$self->color}
+ );
+
+ }
}
sub AUTOLOAD {
@@ -214,6 +234,22 @@ sub tb_nudge {
return $self->{tb_nudge}
}
}
+
+sub orientation {
+ my $self = shift;
+ my $type = ref($self) || die "$self is not an object";
+ unless (exists $self->{orientation} ) {
+ die "Can't find orientation field in object of class $type";
+ }
+
+ if (@_) {
+ return $self->{orientation} = shift;
+ } else {
+ return $self->{orientation}
+ }
+}
+
+
sub DESTROY {
# doing nothing about destruction, hope that isn't dangerous
}
diff --git a/lib/Matrix.pm b/lib/Matrix.pm
index 18c8093ff9..3bb7586c94 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,8 +74,23 @@ sub _stringify {
return($s);
}
-# obtain the Left Right matrices of the decomposition and the two pivot permutation matrices
-# the original is M = PL*L*R*PR
+=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 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
+
sub L {
my $matrix = shift;
my $rows = $matrix->[1];
@@ -83,6 +104,7 @@ sub L {
}
$L_matrix;
}
+
sub R {
my $matrix = shift;
my $rows = $matrix->[1];
@@ -117,12 +139,14 @@ 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
Method $matrix->rh_options
+Meant for internal use when dealing with MatrixReal1
+
=cut
sub rh_options {
@@ -137,9 +161,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 +180,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 +203,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 +216,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 +231,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 +259,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 +273,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 +295,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 +319,8 @@ sub new_column_matrix {
vectors.
Method $matrix->new_from_col_vecs
+
+ Deprecated: The tools for creating MathObjects Matrices are simpler
=cut
@@ -343,8 +371,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 +490,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/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/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..78eb745395 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")}
}
@@ -49,8 +51,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 +64,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;
diff --git a/lib/Parser/Function/numeric.pm b/lib/Parser/Function/numeric.pm
index cfd2b842c4..18bf0fa461 100644
--- a/lib/Parser/Function/numeric.pm
+++ b/lib/Parser/Function/numeric.pm
@@ -52,6 +52,28 @@ sub log {
CORE::log($_[0]);
}
+#
+# Handle reduction of ln(e) and ln(e^x)
+#
+sub _reduce {
+ my $self = shift;
+ my $context = $self->context;
+ my $base10 = $context->flag('useBaseTenLog');
+ my $reduce = $context->{reduction};
+ if ($self->{name} eq 'ln' || ($self->{name} eq 'log' && !$base10)) {
+ my $arg = $self->{params}[0];
+ if ($reduce->{'ln(e^x)'}) {
+ return $arg->{rop} if $arg->isa('Parser::BOP::power') && $arg->{lop}->string eq 'e';
+ return $arg->{params}[0] if $arg->isa('Parser::Function') && $arg->{name} eq 'exp';
+ }
+ return $self->Item('Value')->new($self->{equation}, [$self->eval])
+ if $context->flag('reduceConstantFunctions') && $arg->string eq 'e';
+ }
+ return $self;
+}
+
+$Parser::reduce->{'ln(e^x)'} = 1;
+
#
# Handle absolute values as a special case
#
diff --git a/lib/Parser/Legacy/NumberWithUnits.pm b/lib/Parser/Legacy/NumberWithUnits.pm
index 22052634fa..49247724f9 100644
--- a/lib/Parser/Legacy/NumberWithUnits.pm
+++ b/lib/Parser/Legacy/NumberWithUnits.pm
@@ -59,8 +59,6 @@ sub new {
}
}
-
-
Value::Error("You must provide a ".$self->name) unless defined($num);
($num,$units) = splitUnits($num) unless $units;
Value::Error("You must provide units for your ".$self->name) unless $units;
@@ -71,6 +69,8 @@ sub new {
$num->{units} = $units;
$num->{units_ref} = \%Units;
$num->{isValue} = 1;
+ $num->{correct_ans} .= ' '.$units if defined $num->{correct_ans};
+ $num->{correct_ans_latex_string} .= ' '.TeXunits($units) if defined $num->{correct_ans_latex_string};
bless $num, $class;
}
@@ -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');
diff --git a/lib/Units.pm b/lib/Units.pm
index 0a076c5c83..60b6582492 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
@@ -105,6 +108,22 @@ our %known_units = ('m' => {
'factor' => 0.0174532925,
'rad' => 1
},
+ 'degree' => {
+ 'factor' => 0.0174532925,
+ 'rad' => 1
+ },
+ 'degrees' => {
+ 'factor' => 0.0174532925,
+ 'rad' => 1
+ },
+ 'radian' => {
+ 'factor' => 1,
+ 'rad' => 1
+ },
+ 'radians' => {
+ 'factor' => 1,
+ 'rad' => 1
+ },
'sr' => {
'factor' => 1,
'rad' => 2
@@ -118,6 +137,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
@@ -126,10 +157,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 +245,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 +304,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
@@ -255,6 +330,11 @@ our %known_units = ('m' => {
'm' => 1,
's' => -1
},
+ 'mph' => {
+ 'factor' => 0.44704,
+ 'm' => 1,
+ 's' => -1
+ },
# MASS
# mg -- miligrams
# g -- grams
diff --git a/lib/Value/AnswerChecker.pm b/lib/Value/AnswerChecker.pm
index 21d7651f50..433db03cb7 100644
--- a/lib/Value/AnswerChecker.pm
+++ b/lib/Value/AnswerChecker.pm
@@ -1690,7 +1690,7 @@ sub cmp {
Parser::Context->current(undef,$context);
$context->variables->add('C0' => 'Parameter');
my $f = $self->Package("Formula")->new('C0')+$self;
- for ('limits','test_points','test_values','num_points','granularity','resolution',
+ for ('limits','test_points','test_values','test_at','num_points','granularity','resolution',
'checkUndefinedPoints','max_undefined')
{$f->{$_} = $self->{$_} if defined($self->{$_})}
$cmp->ans_hash(correct_value => $f);
diff --git a/lib/Value/List.pm b/lib/Value/List.pm
index 66ead376c3..79994a2990 100644
--- a/lib/Value/List.pm
+++ b/lib/Value/List.pm
@@ -38,7 +38,7 @@ sub new {
return $self->formula($p) if $isFormula;
my $list = bless {data => $p, type => $type, context=>$context}, $class;
$list->{correct_ans} = $p->[0]{correct_ans}
- if $isSingleton && defined scalar(@{$p}) && defined $p->[0]{correct_ans};
+ if $isSingleton && scalar(@{$p}) && defined $p->[0]{correct_ans};
if (scalar(@{$p}) == 0) {
$list->{open} = $def->{nestedOpen};
$list->{close} = $def->{nestedClose};
diff --git a/lib/Value/Matrix.pm b/lib/Value/Matrix.pm
index e853ff5ff5..79d81532d2 100644
--- a/lib/Value/Matrix.pm
+++ b/lib/Value/Matrix.pm
@@ -3,6 +3,124 @@
# 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 for these methods are done in C
+
+ trace
+ proj
+ proj_coeff
+ L
+ R
+ PL
+ PR
+
+Passthrough methods covering subroutines in C
+(this has been modified to handle complex numbers)
+The actual calculations are done in C subroutines
+The commands below are Value::Matrix B unless otherwise noted.
+
+
+
+ condition
+ det
+ inverse
+ is_symmetric
+ decompose_LR
+ dim
+ norm_one
+ norm_max
+ kleene
+ normalize
+ 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
+
+=cut
+
#
package Value::Matrix;
my $pkg = 'Value::Matrix';
@@ -17,7 +135,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 +156,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 +180,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;
@@ -209,7 +327,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);
@@ -225,7 +343,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
@@ -247,8 +365,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)}
@@ -260,7 +377,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 +556,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 +598,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 {
@@ -487,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/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)}
diff --git a/lib/WeBWorK/PG/Translator.pm b/lib/WeBWorK/PG/Translator.pm
index 180e038d2f..33f99e1221 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/MatrixCheckers.pl b/macros/MatrixCheckers.pl
index 6f4c963173..3b57748745 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.
@@ -144,7 +144,7 @@ sub concatenate_columns_into_matrix {
for my $column (@c) {
push(@temp,Matrix($column)->transpose->row(1));
}
- return Matrix(@temp)->transpose;
+ return Matrix(\@temp)->transpose;
}
@@ -314,8 +314,8 @@ sub basis_checker_rows {
return 0 if scalar(@s) < scalar(@c); # count the number of vector inputs
# These two lines are what is different from basis_checker_columns
- my $C = Matrix(@c)->transpose; # put the rows of @c into columns of $C.
- my $S = Matrix(@s)->transpose; # put the rows of @s into columns of $S.
+ my $C = Matrix(\@c)->transpose; # put the rows of @c into columns of $C.
+ my $S = Matrix(\@s)->transpose; # put the rows of @s into columns of $S.
# Put $C and $S into the local context so that
# all of the computations that follow will also be in
@@ -362,8 +362,8 @@ sub orthonormal_basis_checker_rows {
return 0 if scalar(@s) < scalar(@c); # count the number of vector inputs
# These two lines are what is different from basis_checker_columns
- my $C = Matrix(@c)->transpose; # put the rows of @c into columns of $C.
- my $S = Matrix(@s)->transpose; # put the rows of @s into columns of $S.
+ my $C = Matrix(\@c)->transpose; # put the rows of @c into columns of $C.
+ my $S = Matrix(\@s)->transpose; # put the rows of @s into columns of $S.
# Put $C and $S into the local context so that
# all of the computations that follow will also be in
diff --git a/macros/MatrixReduce.pl b/macros/MatrixReduce.pl
index 010d732269..57e64e4f24 100644
--- a/macros/MatrixReduce.pl
+++ b/macros/MatrixReduce.pl
@@ -14,27 +14,49 @@ =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);>
+
+(This is an alias for Value::Matrix->I(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 multiply row i by s: C<$E = elem_matrix_row_mult(5,2,4);>
-=item Construct an n x n identity matrix: C<$E = identity_matrix(5);>
+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 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 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);> creates a 5 x 5 identity matrix and swaps puts 35 in the (3,1) position.
+=item Construct an n x n elementary matrix that will add s times row j to row i: C<$E3 = elem_matrix_row_add(5,3,1,35);> creates a 5 x 5 identity matrix and 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 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 switch transform that swaps (row i) with (row j): C<$Areduced = row_switch($A,2,4);>
-=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.)
+swaps rows 2 and 4 in matrix $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 +64,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());
+ 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
+ # 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 = 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
+ $A = apply_fraction_to_matrix_entries($A); # try commenting this line out for different results
-$Arref = rref($A);
+ $Arref = rref($A);
-$Aswitch = row_switch($A, 2, 3);
+ $Aswitch = row_switch($A, 2, 3);
-$Amult = row_mult($A, 2, 4);
+ $Amult = row_mult($A, 2, 4);
-$Aadd = row_add($A, 2, 1, 10);
+ $Aadd = row_add($A, 2, 1, 10);
-$E = elem_matrix_row_add(3,2,1,10);
-$EA = $E * $A;
+ $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);
+ $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;
+ 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();
+ COMMENT('MathObject version.');
+ ENDDOCUMENT();
=back
@@ -134,14 +154,14 @@ sub rref {
my $M = shift;
my @m = $M->value;
my @m_reduced = rref_perl_array(@m);
- return Matrix(@m_reduced);
+ return Matrix(\@m_reduced);
}
sub rcef {
my $M = shift;
my @m = $M->transpose->value;
my @m_reduced = rref_perl_array(@m);
- return Matrix(@m_reduced)->transpose;
+ return Matrix(\@m_reduced)->transpose;
}
sub rref_perl_array {
@@ -204,7 +224,7 @@ sub elem_matrix_row_switch {
# $n = number of rows (and columns) in matrix
# $i and $j are indices of rows to be switched (index starting at 1, not 0)
- ($n,$i,$j) = @_;
+ my ($n,$i,$j) = @_;
if ($i < 1 or $j < 1 or $i > $n or $j > $n) {
warn "Index out of bounds in Elem_row_switch(). Returning identity matrix.";
return Value::Matrix->I($n);
@@ -212,7 +232,7 @@ sub elem_matrix_row_switch {
my $M = Value::Matrix->I($n); # construct identity matrix
my @m = $M->value;
@m[$i - 1, $j - 1] = @m[$j - 1, $i - 1]; # switch rows
- return Matrix(@m);
+ return Matrix(\@m);
}
@@ -222,7 +242,7 @@ sub elem_matrix_row_mult {
# $n = number of rows (and columns) in matrix
# $i and $j are indices of rows to be switched (index starting at 1, not 0)
- ($n,$i,$s) = @_;
+ my ($n,$i,$s) = @_;
if ($i < 1 or $i > $n) {
warn "Index out of bounds in elem_row_mult(). Returning identity matrix.";
return Value::Matrix->I($n);
@@ -234,7 +254,7 @@ sub elem_matrix_row_mult {
my $M = Value::Matrix->I($n); # construct identity matrix
my @m = $M->value;
foreach my $rowval ( @{$m[$i - 1]} ) { $rowval *= $s; }
- return Matrix(@m);
+ return Matrix(\@m);
}
@@ -244,7 +264,7 @@ sub elem_matrix_row_add {
# $n = number of rows (and columns) in matrix
# $i and $j are indices of rows to be switched (index starting at 1, not 0)
- ($n,$i,$j,$s) = @_;
+ my ($n,$i,$j,$s) = @_;
if ($i < 1 or $j < 1 or $i > $n or $j > $n) {
warn "Index out of bounds in elem_matrix_row_add(). Returning identity matrix.";
return Value::Matrix->I($n);
@@ -256,7 +276,7 @@ sub elem_matrix_row_add {
my $M = Value::Matrix->I($n); # construct identity matrix
my @m = $M->value;
$m[$i - 1][$j - 1] = $s;
- return Matrix(@m);
+ return Matrix(\@m);
}
@@ -290,7 +310,7 @@ sub row_add {
foreach my $k (0..$c-1) {
$m[$i - 1][$k] += $s * $m[$j - 1][$k];
}
- return Matrix(@m);
+ return Matrix(\@m);
}
@@ -308,7 +328,7 @@ sub row_switch {
}
my @m = $M->value;
@m[$i1 - 1,$i2 - 1] = @m[$i2 - 1,$i1 - 1];
- return Matrix(@m);
+ return Matrix(\@m);
}
@@ -327,7 +347,7 @@ sub row_mult {
if ($s == 0 and $permissionLevel >= 10) { warn "Scaling a row by zero is not a valid row operation. (This warning is only shown to professors.)"; }
my @m = $M->value;
foreach my $rowval ( @{$m[$i - 1]} ) { $rowval *= $s; } # row multiplication
- return Matrix(@m);
+ return Matrix(\@m);
}
@@ -341,7 +361,7 @@ sub apply_fraction_to_matrix_entries {
foreach my $i (0..$r-1) {
foreach my $rowval ( @{$m[$i]} ) { $rowval = Fraction("$rowval"); }
}
- return Matrix(@m);
+ return Matrix(\@m);
}
diff --git a/macros/MatrixUnits.pl b/macros/MatrixUnits.pl
index 810d06f5ee..36959d0402 100644
--- a/macros/MatrixUnits.pl
+++ b/macros/MatrixUnits.pl
@@ -38,7 +38,7 @@ =head1 DESCRIPTION
Note that the indexing on MathObject matrices starts at 1, while the indexing
on perl arrays starts at 0, so that C<$A-element(1,1);> corresponds to
C<$a[0][0];>. The perl arrays can be made into MathObject matrices by
-C<$A = Matrix(@a);>, and this is, in fact, what the C and C
+C<$A = Matrix(\@a);>, and this is, in fact, what the C and C
subroutines do for you. The perl versions C<@a = GLnZ_perl()> and
C<@a = SLnZ_perl()> are useful if you want to have quick access to the matrix
values (as perl reals stored in C<@a>) without having to pull them out of a
@@ -65,7 +65,7 @@ =head1 AUTHORS
sub GL2Z {
my @a = GL2Z_perl();
- return Matrix(@a);
+ return Matrix(\@a);
}
sub GL2Z_perl {
@@ -88,7 +88,7 @@ sub GL2Z_perl {
sub SL2Z {
my @a = SL2Z_perl();
- return Matrix(@a);
+ return Matrix(\@a);
}
sub SL2Z_perl {
@@ -116,7 +116,7 @@ sub SL2Z_perl {
sub GL3Z {
my @a = GL3Z_perl();
- return Matrix(@a);
+ return Matrix(\@a);
}
sub GL3Z_perl {
@@ -151,7 +151,7 @@ sub GL3Z_perl {
sub SL3Z {
my @a = SL3Z_perl();
- return Matrix(@a);
+ return Matrix(\@a);
}
sub SL3Z_perl {
@@ -190,7 +190,7 @@ sub SL3Z_perl {
sub GL4Z {
my @a = GL4Z_perl();
- return Matrix(@a);
+ return Matrix(\@a);
}
@@ -243,7 +243,7 @@ sub GL4Z_perl {
sub SL4Z {
my @a = SL4Z_perl();
- return Matrix(@a);
+ return Matrix(\@a);
}
sub SL4Z_perl {
diff --git a/macros/PGML.pl b/macros/PGML.pl
index d773fcdbd1..e7971e1086 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 =
@@ -276,7 +276,7 @@ sub ForceBreak {
sub Par {
my $self = shift; my $token = shift;
- $self->End;
+ $self->End(null, shift);
$self->Item("par",$token,{noIndent => 1});
$self->{atLineStart} = $self->{ignoreNL} = 1;
$self->{indent} = $self->{actualIndent} = 0;
@@ -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}}
@@ -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"]},
@@ -532,7 +537,7 @@ sub terminatePre {
my $self = shift; my $token = shift;
$self->{block}{terminator} = ''; # we add the ending token to the text below
if ($token =~ m/\n\n/) {
- $self->Par($token);
+ $self->Par($token, $self->{block});
$self->{block} = $self->{block}{prev};
} else {
$self->Text($token);
@@ -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 {
@@ -1094,6 +1100,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"',
);
@@ -1187,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(@_));
}
######################################################################
@@ -1327,11 +1334,139 @@ sub Verbatim {
return $text;
}
+
sub Math {
my $self = shift;
- return main::math_ev3($self->SUPER::Math(@_));
+ return main::general_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 .
+ "".substr($list,0,2).">\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/</g;
+ $text =~ s/&/&/g;
+ $text = "$text";
+ return $text;
+}
+
+sub Math {
+ my $self = shift;
+ return main::general_math_ev3($self->SUPER::Math(@_));
+}
+
+
######################################################################
######################################################################
@@ -1343,6 +1478,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";
}
@@ -1401,6 +1539,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}%
diff --git a/macros/PGbasicmacros.pl b/macros/PGbasicmacros.pl
index 20727e9271..374ecc285b 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();
@@ -396,9 +402,10 @@ 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!,
+ PTX => '',
);
}
@@ -439,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
@@ -488,8 +496,9 @@ sub NAMED_ANS_RULE_EXTENSION {
MODES(
TeX => "\\mbox{\\parbox[t]{${tcol}ex}{\\hrulefill}}",
Latex2HTML => qq!\\begin{rawhtml}\n\n\\end{rawhtml}\n!,
- HTML => qq!!.
- qq!!
+ HTML => qq!!.
+ qq!!,
+ PTX => '',
);
}
@@ -530,7 +539,8 @@ sub ANS_RULE { #deprecated
HTML => qq!
- !
+ !,
+ PTX => '',
);
$out;
}
@@ -565,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",
);
}
@@ -604,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",
);
}
@@ -765,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",
);
}
@@ -802,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 => '
'."\n";
+ };
+ $out .= '';
}
$name = RECORD_ANS_NAME($name,$answer_value); # record answer name
$out;
@@ -1049,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
@@ -1129,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!!,
);
}
@@ -1199,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(...));
@@ -1207,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.
@@ -1266,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(@_) ;
}
@@ -1309,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 ;
}
@@ -1321,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
@@ -1447,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", ],
@@ -1518,6 +1578,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
@@ -1546,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}
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;
+ #ditto for li
+ $string =~ s/(
]*(?)/$1\n
/g;
+ $string =~ s/(<\/li>)/<\/p>\n$1/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;
+
+ #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
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|
)((?!<\/tabular>).)*?((?!<\/tabular>).)*?)
(((?!<\/tabular>).)*?)<\/p>(((?!<\/tabular>).)*?<\/tabular>)/$1$4$6/g;
+ } until ($previous eq $string);
+
+ };
+ $string;
+}
+
+sub PTX_special_character_cleanup {
+ my $string = shift;
+ $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 =~ 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
@@ -2224,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 ",
@@ -2239,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;
}
@@ -2302,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) {
@@ -2318,15 +2470,17 @@ sub OL {
#HTML=> "
\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..7982aa7eaa 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
@@ -291,6 +225,9 @@ sub dm_begin_matrix {
or $main::displayMode eq 'HTML_img') {
$out .= qq!
$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";
}
@@ -692,6 +644,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 +668,17 @@ 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 +724,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 +738,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 (Value::isParser($input) and Value::classMatch($input,"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 +790,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 +808,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..c6222b40ec 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,18 @@ sub row_mult{
return $B;
}
+=head4 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.
+
+=cut
+
sub linear_combo{
warn "Usage: \$new_matrix = linear_combo(\$matrix,\$scalar,\$row1,\$row2);"
@@ -106,6 +156,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 +437,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 +564,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 +683,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/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/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
###########################################################################
#
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 {
", 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=>'',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/contextCurrency.pl b/macros/contextCurrency.pl
index 3690c362ba..7c81ab918a 100644
--- a/macros/contextCurrency.pl
+++ b/macros/contextCurrency.pl
@@ -247,7 +247,7 @@ sub new {
$context->operators->remove($symbol) if $context->operators->get($symbol);
$context->operators->add(
$symbol => {precedence => 10, associativity => $associativity, type => "unary",
- string => ($main::displayMode eq 'TeX' ? Currency::quoteTeX($symbol) : $symbol),
+ string => (($main::displayMode eq 'TeX' or $main::displayMode eq 'PTX') ? Currency::quoteTeX($symbol) : $symbol),
TeX => Currency::quoteTeX($symbol), class => 'Currency::UOP::currency'},
);
$context->{parser}{Number} = "Currency::Number";
diff --git a/macros/contextLimitedNumeric.pl b/macros/contextLimitedNumeric.pl
index 725fc78259..38bb3be0ad 100644
--- a/macros/contextLimitedNumeric.pl
+++ b/macros/contextLimitedNumeric.pl
@@ -28,9 +28,9 @@ =head1 DESCRIPTION
one of the following commands:
Context("LimitedNumeric-List");
- Context("LimiteNumeric");
+ Context("LimitedNumeric");
-(Now uses Parcer::Legacy::LimitedNumeric to implement
+(Now uses Parser::Legacy::LimitedNumeric to implement
these contexts.)
=cut
diff --git a/macros/contextPermutation.pl b/macros/contextPermutation.pl
index b118010044..eda3d87fb2 100644
--- a/macros/contextPermutation.pl
+++ b/macros/contextPermutation.pl
@@ -28,7 +28,7 @@ =head1 DESCRIPTION
parentheses. Cycles are multiplied by juxtaposition. A permutation
can be multiplied on the left by a number in order to obtain the
result of that number under the action of the permutation.
-Exponentiation is alos allowed (as described below).
+Exponentiation is also allowed (as described below).
There are three contexts included here: C, which
allows permutations in any form, C, which
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
diff --git a/macros/customizeLaTeX.pl b/macros/customizeLaTeX.pl
new file mode 100644
index 0000000000..7b22290d98
--- /dev/null
+++ b/macros/customizeLaTeX.pl
@@ -0,0 +1,130 @@
+=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
+
+sub _customizeLaTeX_init {
+
+} #prevents this file from being loaded twice.
+
+##### Set theory macros
+sub set_minus{
+ #return "\\setminus";
+ return '-';
+};
+
+##### Logic macros
+sub negate {
+ return "\\mathbin{\\sim}";
+ #return "\\lnot";
+};
+
+sub implies {
+ return "\\implies";
+ #return "\\Rightarrow";
+}
+
+##### Linear algebra macros
+
+sub vectorstyle {
+ my $v = shift;
+ return "\\vec{$v}"
+ #return "$v";
+}
+
+sub polynomials_of_degree_up_to_degree_over_ring_in_variable {
+ my ($n, $R, $variable) = @_;
+ return $R."[".$variable."]_{\\mathrm{Grad} \\leq ".$n."}";
+}
+
+sub matrix_of_homomorphism_with_respect_to_bases {
+ my ($homomorphism, $basis_source, $basis_target) = @_;
+ return "{}^{$basis_target}{".$homomorphism."}^{$basis_source}";
+}
+
+sub coordinates_of_vector_with_respect_to_basis {
+ my ($vector, $basis) = @_;
+ return "{}^{$basis}{(".$vector.")}";
+}
+
+sub span {
+ my ($set) = @_;
+ return "\\langle $set \\rangle";
+}
+
+sub matrices_over_ring {
+ my ($rows, $columns, $ring) = @_;
+ return "{$ring}^{$rows \\times $columns}";
+ # return "M_{$rows \\times $columns}($ring)";
+ # return "M_{$rows, $columns}($ring)";
+}
+
+##### 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}";
+
+};
+
+# Macro to display the ring Z/nZ
+sub ZmodnZ {
+ my $n = shift;
+ 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;
diff --git a/macros/niceTables.pl b/macros/niceTables.pl
index b2ea8bec37..571d809222 100644
--- a/macros/niceTables.pl
+++ b/macros/niceTables.pl
@@ -15,7 +15,7 @@
##
## NOTE: In order to reduce separate setting of on-screen and hard copy settings as much as possible, Perl 5.10+
## tools are used. These macros may behave unexpectedly or not work at all with older versions of Perl.
-## These macros use LaTeX packages inthe hard copy that wer not formerly part of a WeBWorK hard copy preamble.
+## These macros use LaTeX packages in the hard copy that wer not formerly part of a WeBWorK hard copy preamble.
## Your LaTeX distribution needs to have the packages: booktabs, tabularx, colortbl, caption, xcolor
## And if you have a WeBWorK version earlier than 2.10, you need to add calls to these packages to hardcopyPreamble.tex
## in webwork2/conf/snippets/
@@ -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 .= '
'.$caption.'
';}
+ $ptxtable = "\n\n";
$table .= '
';
for my $i (0..$#{$columnscss})
{$columnscss->[$i] = '' unless (defined($columnscss->[$i]));
- $table .= '
';};
+ $table .= '
';
+ };
$table .= '
';
+ if ($caption ne '') {
+ $table .= '
'.$caption.'
';
+ # 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 .= '
'. "\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..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}};
@@ -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/problemRandomize.pl b/macros/problemRandomize.pl
index 26e3a98546..7483a999a6 100644
--- a/macros/problemRandomize.pl
+++ b/macros/problemRandomize.pl
@@ -182,9 +182,9 @@ sub new {
style => "Button",
styleName => ($main::inputs_ref->{effectiveUser} ne $main::inputs_ref->{user} ? "checkAnswers" : "submitAnswers"),
label => undef,
- buttonLabel => "Get a new version of this problem",
- checkboxLabel => "Get a new version of this problem",
- inputLabel => "Set random seed to:",
+ buttonLabel => $main::PG->maketext("Get a new version of this problem"),
+ checkboxLabel => $main::PG->maketext("Get a new version of this problem"),
+ inputLabel => $main::PG->maketext("Set random seed to:"),
grader => $main::PG->{flags}->{PROBLEM_GRADER_TO_USE} || \&main::avg_problem_grader, #$main::PG_FLAGS{PROBLEM_GRADER_TO_USE}
random => $main::PG_random_generator,
status => {},
@@ -379,8 +379,8 @@ sub grader {
# Add the problemRandomize message and data
#
if ($isWhen && !$okDate) {
- $result->{msg} .= " Note:" if $result->{msg};
- $result->{msg} .= "You can get a new version of this problem after the due date.";
+ $result->{msg} .= " ".$main::PG->maketext("Note:")."" if $result->{msg};
+ $result->{msg} .= $main::PG->maketext("You can get a new version of this problem after the due date.");
}
if (!$result->{msg}) {
# hack to remove unwanted "Note: " from the problem
@@ -404,9 +404,9 @@ sub grader {
if ($self->{isReset} && $isSubmit) {
$result->{msg} .= "";
$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.");
}
#
diff --git a/macros/sage.pl b/macros/sage.pl
index dd56acc047..03d5842e9a 100644
--- a/macros/sage.pl
+++ b/macros/sage.pl
@@ -5,33 +5,155 @@
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',
- 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;
+
+# 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
+
+
+ # Create python/sage function "record_answer()"
+ # to print a WeBWorK answer blank from within
+ # the sage interact. The function is different depending on whether the answer blank exists
+ # and whether it is visible.
+
+ # (1) $recordAnswerBlank will 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 ($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
+
+
+ #FIXME -- for some reason the answer blank, printed with pretty_print
+ # floats to the top of the printed Sage block above print statements.
+ # ???? This can be intermittent, which is even more surprising.
+
+ # (2) now determine whether the record_answer() prints an answer blank
+ # or is a noop - a dummy operation.
+
+ if ($recordAnswerBlank eq 'none') {
+ $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;
- main::RECORD_ANS_NAME($self->{SageAnswerName}, 345);
$self->sageCode();
$self->sagePrint();
@@ -39,18 +161,22 @@ sub new {
}
+# 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"));
-
-
@@ -77,7 +203,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 {
@@ -86,13 +247,10 @@ sub sageCalculatorHeader {
@@ -101,14 +259,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
-
+
"));
+ 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 => [
'
',
- 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