diff --git a/conf/pg_config.dist.yml b/conf/pg_config.dist.yml
index f8d4735028..f2f3d85542 100644
--- a/conf/pg_config.dist.yml
+++ b/conf/pg_config.dist.yml
@@ -41,28 +41,17 @@ directories:
macrosPath:
- .
- $pg_root/macros
- - $OPL_dir/macros/Alfred
- - $OPL_dir/macros/BrockPhysics
- - $OPL_dir/macros/CollegeOfIdaho
- - $OPL_dir/macros/Dartmouth
- - $OPL_dir/macros/FortLewis
- - $OPL_dir/macros/Hope
- - $OPL_dir/macros/LaTech
- - $OPL_dir/macros/MC
- - $OPL_dir/macros/Michigan
- - $OPL_dir/macros/Mizzou
- - $OPL_dir/macros/NAU
- - $OPL_dir/macros/PCC
- - $OPL_dir/macros/TCNJ
- - $OPL_dir/macros/UBC
- - $OPL_dir/macros/UMass-Amherst
- - $OPL_dir/macros/UW-Stout
- - $OPL_dir/macros/UniSiegen
- - $OPL_dir/macros/Union
- - $OPL_dir/macros/WHFreeman
- - $OPL_dir/macros/Wiley
- - $Contrib_dir/CAPA/macros/CAPA_Tools
- - $Contrib_dir/CAPA/macros/CAPA_MCTools
+ - $pg_root/macros/answers
+ - $pg_root/macros/capa
+ - $pg_root/macros/contexts
+ - $pg_root/macros/core
+ - $pg_root/macros/graph
+ - $pg_root/macros/math
+ - $pg_root/macros/misc
+ - $pg_root/macros/parsers
+ - $pg_root/macros/ui
+ - $pg_root/macros/deprecated
+
URLs:
# The public URL of the html directory above.
diff --git a/doc/MathObjects/macros/parserTables.pl b/doc/MathObjects/macros/parserTables.pl
deleted file mode 100644
index 22b9b05a6d..0000000000
--- a/doc/MathObjects/macros/parserTables.pl
+++ /dev/null
@@ -1,90 +0,0 @@
-loadMacros("parserUtils.pl");
-
-#############################################
-#
-# For Parser example tables:
-#
-
-$BTT = MODES(TeX => '{\tt ', Latex2HTML => $bHTML . '' . $eHTML, HTML => '');
-$ETT = MODES(TeX => '}', Latex2HTML => $bHTML . ' ' . $eHTML, HTML => ' ');
-
-$BC = MODES(
- TeX => '{\small\it ',
- Latex2HTML => $bHTML . '' . $eHTML,
- HTML => ''
-);
-$EC = MODES(
- TeX => '}',
- Latex2HTML => $bHTML . ' ' . $eHTML,
- HTML => ' '
-);
-
-$LT = MODES(TeX => "<", Latex2HTML => "<", HTML => '<');
-$GT = MODES(TeX => ">", Latex2HTML => ">", HTML => '>');
-
-$TEX = MODES(TeX => '{\TeX}', HTML => 'TeX', HTML_dpng => '\(\bf\TeX\)');
-
-@rowOptions = (
- indent => 0,
- separation => 0,
- align => 'LEFT" NOWRAP="1', # alignment hack to get NOWRAP
-);
-
-sub ParserRow {
- my $f = shift;
- my $t = '';
- Context()->clearError;
- my ($s, $err) = PG_restricted_eval($f);
- if (defined $s) {
- my $ss = $s;
- if (ref($s) && \&{ $s->string }) {
- $t = '\(' . $s->TeX . '\)';
- $s = $s->string;
- } elsif ($s !~ m/^[a-z]+$/i) {
- $t = '\(' . Formula($s)->TeX . '\)';
- $s = Formula($s)->string;
- }
- $s =~ s/$LT/g;
- $s =~ s/>/$GT/g;
- if (ref($ss) && \&{ $ss->class }) {
- if ($ss->class eq 'Formula') {
- $s .= ' ' . $BC . '(Formula returning ' . $ss->showType . ')' . $EC;
- } else {
- $s .= ' ' . $BC . '(' . $ss->class . ' object)' . $EC;
- }
- }
- } else {
- $s = $BC . (Context()->{error}{message} || $err) . $EC;
- $t = '';
- }
- $f =~ s/$LT/g;
- $f =~ s/>/$GT/g;
- if ($displayMode eq 'TeX') {
- $f =~ s/\^/\\char`\\^/g;
- $s =~ s/\^/\\char`\\^/g;
- $f =~ s/#/\\#/g;
- $s =~ s/#/\\#/g;
- }
- my $row = Row([ $BTT . $f . $ETT, $BTT . $s . $ETT, $t ], @rowOptions);
- $row =~ s/\$/\${DOLLAR}/g;
- return $row;
-}
-
-sub ParserTable {
- my $table = BeginTable(border => 1, padding => 20)
- . Row([ $BBOLD . "Perl Code" . $EBOLD, $BBOLD . "Result" . $EBOLD, $BBOLD . $TEX . ' version' . $EBOLD ],
- @rowOptions);
- foreach my $f (@_) { $table .= ParserRow($f) }
- $table .= EndTable();
- return $table;
-}
-
-sub Title {
- my $title = shift;
-
- MODES(
- TeX => "\\par\\centerline{\\bf $title}\\par\\nobreak\n",
- Latex2HTML => $bHTML . '
' . $title . ' ' . $eHTML,
- HTML => '' . $title . ' '
- );
-}
diff --git a/doc/MathObjects/macros/unionTables.pl b/doc/MathObjects/macros/unionTables.pl
deleted file mode 100644
index 456964cdd5..0000000000
--- a/doc/MathObjects/macros/unionTables.pl
+++ /dev/null
@@ -1,276 +0,0 @@
-######################################################################
-##
-## Functions for creating tables of various kinds
-##
-## ColumnTable() Creates a two-column display in HTML,
-## but only one column in TeX.
-##
-## ColumnMatchTable() Does a side-by-side match table
-##
-## BeginTable() Begin a borderless HTML table
-## Row() Create a row in the table
-## AlignedRow() Create a row with alignment in each column
-## TableSpace() Insert extra vertical space in the table
-## EndTable() End the table
-##
-
-######################################################################
-#
-# Make a two-column table in HTML and Latex2HTML modes
-#
-# Usage: ColumnTable(col1,col2,[options])
-#
-# Options can be taken from:
-#
-# indent => n the width to indent the first column
-# (default is 0)
-#
-# separation => n the width of the separating gutter
-# (default is 50)
-#
-# valign => type set the vertical alignment
-# (default is "MIDDLE")
-#
-sub ColumnTable {
- my $col1 = shift;
- my $col2 = shift;
- my %options = (indent => 0, separation => 50, valign => "MIDDLE", @_);
- my ($ind, $sep) = ($options{"indent"}, $options{"separation"});
- my $valign = $options{"valign"};
-
- my ($bhtml, $ehtml) = ('\begin{rawhtml}', '\end{rawhtml}');
- ($bhtml, $ehtml) = ('', '') unless ($displayMode eq "Latex2HTML");
-
- my $HTMLtable = qq {
- $bhtml
- $ehtml
- $col1
- $bhtml $ehtml
- $col2
- $bhtml
$ehtml
- };
-
- MODES(
- TeX => '\par\medskip\hbox{\qquad\vtop{'
- . '\advance\hsize by -3em '
- . $col1 . '}}'
- . '\medskip\hbox{\qquad\vtop{'
- . '\advance\hsize by -3em '
- . $col2
- . '}}\medskip',
- Latex2HTML => $HTMLtable,
- HTML => $HTMLtable
- );
-}
-
-#
-# Use columns for a match-list output
-#
-# Usage: ColumnMatchTable($ml,options)
-#
-# where $ml is a math list reference and options are those
-# allowed for ColumnTable above.
-#
-sub ColumnMatchTable {
- my $ml = shift;
-
- ColumnTable($ml->print_q, $ml->print_a, @_);
-}
-
-#
-# Command for tables with no borders.
-#
-# Usage: BeginTable(options);
-#
-# Options are taken from:
-#
-# border => n value for BORDER attribute (default 0)
-# spacing => n value for CELLSPACING attribute (default 0)
-# padding => n value for CELLPADDING attribute (default 0)
-# tex_spacing => dimen value for spacing between columns in TeX
-# (e.g, tex_spacing => 2em) (default 1em)
-# tex_border => dimen value for left- and right border in TeX (0pt)
-# center => 0 or 1 center table or not (default 1)
-#
-sub BeginTable {
- my %options = (
- border => 0,
- padding => 0,
- spacing => 0,
- center => 1,
- tex_spacing => "1em",
- tex_border => "0pt",
- @_
- );
- my ($bd, $pd, $sp) = ($options{border}, $options{padding}, $options{spacing});
- my ($tsp, $tbd) = ($options{tex_spacing}, $options{tex_border});
- my ($center, $tcenter) = (' ALIGN="CENTER"', '\centerline');
- ($center, $tcenter) = ('', '') if (!$options{center});
- my $table = qq{};
-
- MODES(
- TeX => '\par\medskip' . $tcenter . '{\kern ' . $tbd . '\vbox{\halign{#\hfil&&\kern ' . $tsp . ' #\hfil',
- Latex2HTML => $bHTML . $table . $eHTML . "\n",
- HTML => $table . "\n"
- );
-}
-
-#
-# Usage: EndTable(options)
-#
-# where options are taken from:
-#
-# tex_border => dimen extra vertical space in TeX mode (default 0pt)
-#
-sub EndTable {
- my %options = (tex_border => "0pt", @_);
- my $tbd = $options{tex_border};
- MODES(
- TeX => '\cr}}\kern ' . $tbd . '}\medskip' . "\n",
- Latex2HTML => $bHTML . '
' . $eHTML . "\n",
- HTML => '' . "\n"
- );
-}
-
-#
-# Creates a row in the table
-#
-# Usage: Row([item1,item2,...],options);
-#
-# Each item appears as a separate entry in the table.
-#
-# Options control how the row is displayed:
-#
-# indent => num Specifies size of blank column on the left
-# (default: indent => 0)
-#
-# separation => num Specifies separation of columns
-# (default: spearation => 30)
-#
-# align => "type" Specifies alignment of initial column
-# (default: align => "LEFT")
-#
-# valign => "type" Specified vertical alignment of row
-# (default: valign => "MIDDLE")
-#
-sub Row {
- my $rowref = shift;
- my @row = @{$rowref};
- my %options = (
- indent => 0,
- separation => 30,
- align => "LEFT",
- valign => "MIDDLE",
- @_
- );
- my ($cind, $csep) = ($options{indent}, $options{separation});
- my ($align, $valign) = ($options{align}, $options{valign});
- my $sep = ' ';
- $sep = '' if ($csep < 1);
- my $ind = ' ';
- $ind = '' if ($cind < 1);
- my $fill = '';
- $fill = '\hfil' if (uc($align) eq "CENTER");
- $fill = '\hfill' if (uc($align) eq "RIGHT");
-
- MODES(
- TeX => "\\cr\n" . $fill . join('& ', @row),
- Latex2HTML => $bHTML
- . "$ind"
- . $eHTML
- . join($bHTML . " $sep" . $eHTML, @row)
- . $bHTML
- . ' '
- . $eHTML . "\n",
- HTML => "$ind"
- . join(" $sep", @row)
- . ' ' . "\n"
- );
-}
-
-#
-# AlignedRow([item1,item2,...],options);
-#
-# Options control how the row is displayed:
-#
-# indent => num Specifies size of blank column on the left
-# (default: indent => 0)
-#
-# separation => num Specifies separation of columns
-# (default: spearation => 30)
-#
-# align => "type" Specifies alignment of all columns
-# (default: align => "CENTER")
-#
-# valign => "type" Specified vertical alignment of row
-# (default: valign => "MIDDLE")
-#
-sub AlignedRow {
- my $rowref = shift;
- my @row = @{$rowref};
- my %options = (
- indent => 0,
- separation => 30,
- align => "CENTER",
- valign => "MIDDLE",
- @_
- );
- my ($cind, $csep) = ($options{indent}, $options{separation});
- my ($align, $valign) = ($options{align}, $options{valign});
- my $sep = ' ';
- $sep = '' if ($csep < 1);
- my $ind = ' ';
- $ind = '' if ($cind < 1);
- my $fill = '';
- $fill = '\hfil ' if (uc($align) eq "CENTER");
- $fill = '\hfill ' if (uc($align) eq "RIGHT");
-
- MODES(
- TeX => "\\cr\n" . $fill . join('&' . $fill, @row),
- Latex2HTML => $bHTML
- . "$ind"
- . $eHTML
- . join($bHTML . " $sep" . $eHTML, @row)
- . $bHTML
- . ' '
- . $eHTML . "\n",
- HTML => "$ind"
- . join(" $sep", @row)
- . ' ' . "\n"
- );
-}
-
-#
-# Add extra space between rows of a table
-#
-# Usage: TableSpace(pixels,points)
-#
-# where pixels is the number of pixels of space in HTML mode and
-# points is the number of points to use in TeX mode.
-#
-sub TableSpace {
- my $rsep = shift;
- my $tsep = shift;
- $rsep = $tsep if (defined($tsep) && $main::displayMode eq "TeX");
- return "" if ($rsep < 1);
- MODES(
- TeX => '\vadjust{\kern ' . $rsep . 'pt}' . "\n",
- Latex2HTML => $bHTML . ' ' . $eHTML . "\n",
- HTML => ' ' . "\n",
- );
-}
-
-#
-# A horizontal rule within a table. (Could have been a variable,
-# but all the other table commands are subroutines, so kept it
-# one to be consistent.)
-#
-sub TableLine {
- MODES(
- TeX => '\vadjust{\kern2pt\hrule\kern2pt}',
- Latex2HTML => $bHTML . ' ' . $eHTML . "\n",
- HTML => ' ' . "\n"
- );
-}
-
-1;
diff --git a/lib/PGloadfiles.pm b/lib/PGloadfiles.pm
index 14efed150f..5b14c199d0 100644
--- a/lib/PGloadfiles.pm
+++ b/lib/PGloadfiles.pm
@@ -21,7 +21,7 @@
loadMacros(@macroFiles)
-loadMacros takes a list of file names and evaluates the contents of each file.
+loadMacros takes a list of file names and evaluates the contents of each file.
This is used to load macros which define and augment the PG language. The macro
files are searched for in the directories specified by the array referenced by
$macrosPath, which by default is the current course's macros directory followed
diff --git a/macros/README.md b/macros/README.md
new file mode 100644
index 0000000000..7968c9a66f
--- /dev/null
+++ b/macros/README.md
@@ -0,0 +1,208 @@
+# Structure of the macros directory
+
+This directory now has a subdirectory structure for clarification.
+
+- **answers** answer and evaluator macros
+- **capa** any CAPA related macros
+- **contexts** context related macros
+- **core** macros related to core PG functionality
+- **deprecated** macros that have been labelled deprecated
+- **graph** graphing/graphical related macros.
+- **math** mathematical related macros
+- **parsers** macros that handle parsing
+- **ui** macros that affect the UI in the PG problem
+
+## These Have Been Copied
+
+- `OPL/BrockPhysics/fixedPrecision.pl` to `PG/macros/contexts`
+- `OPL/BrockPhysics/BrockPhysicsMacros.pl` to `PG/macros/deprecated`
+- `OPL/Dartmouth/Dartmouthmacros.pl` to `PG/macros/deprecated`
+- `OPL/Hope/PGgraphgrid.pl` to `PG/macros/graph`
+- `OPL/Hope/VectorListCheckers.pl` to `PG/macros/math`
+- `OPL/LaTech/SI_property_tables.pl` to `PG/macros/math`
+- `OPL/LaTech/interpMacros.pl` to `PG/macros/math`
+- `OPL/Mizzou/MUHelp.pl` to `PG/macros/deprecated`
+- `OPL/TCNJ/Generic.pl` to `PG/macros/answers`
+- `OPL/UBC/regrfnsPG.pl` to `PG/macros/math`
+- `OPL/UMass-Amherst/algebraMacros.pl` to `PG/macros/math`
+- `OPL/WHFreeman/freemanMacros.pl` to `PG/macros/deprecated`
+- `OPL/Michigan/hhAdditionalMacros.pl` to `PG/macros/deprecated`
+- `OPL/Wiley/littleneck.pl` to `PG/macros/deprecated`
+
+### CollegeOfIdaho copied
+
+- `OPL/CollegeOfIdaho/CofIdaho_macros.pl` to `PG/macros/deprecated`
+- `OPL/CollegeOfIdaho/contextInequalitiesAllowStrings.pl` to `PG/macros/contexts`
+- `OPL/CollegeOfIdaho/contextFunctionAssign.pl` to `PG/macros/contexts`
+- `OPL/CollegeOfIdaho/parserUtils.pl` to `PG/macros/parsers`
+- `OPL/CollegeOfIdaho/unorderedAnswer.pl` to `PG/macros/answers`
+
+### FortLewis copied
+
+- `OPL/FortLewis/AnswerFormatHelp.pl` to `PG/macros/answers`
+- `OPL/FortLewis/ConditionalHint.pl` to `PG/macros/answers`
+- `OPL/FortLewis/LiveGraphicsCylindricalPlot3D.pl` to `PG/macros/graph`
+- `OPL/FortLewis/LiveGraphicsParametricCurve3D.pl` to `PG/macros/graph`
+- `OPL/FortLewis/LiveGraphicsParametricSurface3D.pl` to `PG/macros/graph`
+- `OPL/FortLewis/LiveGraphicsVectorField2D.pl` to `PG/macros/graph`
+- `OPL/FortLewis/LiveGraphicsVectorField3D.pl` to `PG/macros/graph`
+- `OPL/FortLewis/MatrixUnimodular.pl` to `PG/macros/math`
+- `OPL/FortLewis/PeriodicRerandomization.pl` to `PG/macros/core`
+
+### NAU copied
+
+- `OPL/NAU/PGnauBinpacking.pl` to `PG/macros/math`
+- `OPL/NAU/PGnauGraphtheory.pl` to `PG/macros/math`
+- `OPL/NAU/PGnauGraphCatalog.pl` to `PG/macros/math`
+- `OPL/NAU/PGnauScheduling.pl` to `PG/macros/math`
+- `OPL/NAU/PGnauSet.pl` to `PG/macros/math`
+- `OPL/NAU/PGnauStats.pl` to `PG/macros/math`
+- `OPL/NAU/PGnauGraphics.pl` to `PG/macros/graph`
+
+### Union copied
+
+- `OPL/Union/answerUtils.pl` to `PG/macros/answers`
+- `OPL/Union/PGunion.pl` to `PG/macros/misc`
+- `OPL/Union/unionImage.pl` to `PG/macros/graph`
+- `OPL/Union/unionInclude.pl` to `PG/macros/misc`
+- `OPL/Union/unionMacros.pl` to `PG/macros/misc`
+- `OPL/Union/unionLists.pl` to `PG/macros/ui`
+- `OPL/Union/unionProblem.pl` to `PG/macros/misc`
+- `OPL/Union/unionTable.pl` to `PG/macros/ui`
+- `OPL/Union/unionUtils.pl` to `PG/macros/misc`
+- `OPL/Union/imageChoice.pl` to `PG/macros/graph`
+- `OPL/FortLewis/AnswerHelp.pl` to `PG/macros/answers`
+
+### PCC copied
+
+- `OPL/PCC/contextFiniteSolutionSets.pl` to `PG/macros/contexts`
+- `OPL/PCC/contextForm.pl` to `PG/macros/contexts`
+- `OPL/PCC/contextLimitedRadicalComplex.pl` to `PG/macros/contexts`
+- `OPL/PCC/contextRationalExponent.pl` to `PG/macros/contexts`
+- `OPL/PCC/contextRestrictedDomains.pl` to `PG/macros/contexts`
+- `OPL/PCC/PCCfactor.pl` to `PG/macros/math`
+- `OPL/PCC/PCCgraphMacros.pl` to `PG/macros/graph`
+- `OPL/PCC/PCCmacros.pl` to `PG/macros/misc`
+- `OPL/PCC/pccTables.pl` to `PG/macros/ui`
+- `OPL/PCC/SolveLinearEquationPCC.pl` to `PG/macros/math`
+- `OPL/PCC/SystemsOfLinearEquationsProblemPCC.pl` to `PG/macros/math`
+
+## The following have not been moved
+
+- `OPL/Hope/MatrixCheckers.pl` (already in PG/math)
+- `OPL/Hope/MatrixReduce.pl` (already in PG/math)
+- `OPL/Hope/MatrixUnits.pl` (already in PG/math)
+- `OPL/MC/draggableProof.pl` (already in PG/math)
+- `OPL/UBC/RserveClient.pl` (already in PG/core)
+- `OPL/UW-Stout/*.pl` (no problems in OPL/Contrib)
+- `OPL/UniSiegen/logicMacros.pl` (no problems in OPL/Contrib)
+- `OPL/Alfred/Alfredmacros.pl` (not relevant)
+- `OPL/BrockPhysics/BrockPhysicsMacros.pl` (not relevant)
+
+### CollegeOfIdaho not moved
+
+- `OPL/CollegeOfIdaho/allPlotMacros.pl` (no problems in OPL/Contrib)
+- `OPL/CollegeOfIdaho/answerUtils.pl` (used newer Union version instead)
+- `OPL/CollegeOfIdaho/choiceUtils.pl` (used Union version instead)
+- `OPL/CollegeOfIdaho/compositionAnswer.pl` (not used in OPL/Contrib)
+- `OPL/CollegeOfIdaho/compoundProblem.pl` (already version in PG/core)
+- `OPL/CollegeOfIdaho/courseHeaders.pl` (not used in OPL/Contrib)
+- `OPL/CollegeOfIdaho/Differentiation.pl` (not used in OPL/Contrib)
+- `OPL/CollegeOfIdaho/DifferentiationDefs.pl` (not used in OPL/Contrib)
+- `OPL/CollegeOfIdaho/diffquotientAnswer.pl` (not used in OPL/Contrib)
+- `OPL/CollegeOfIdaho/ev3p.pl` (not used in OPL/Contrib)
+- `OPL/CollegeOfIdaho/imageChoice.pl` (used Union version -- identical)
+- `OPL/CollegeOfIdaho/infiniteAnswer.pl` (not used in OPL/Contrib)
+- `OPL/CollegeOfIdaho/integerAnswer.pl` (not used in OPL/Contrib)
+- `OPL/CollegeOfIdaho/intervalAnswer.pl` (not used in OPL/Contrib)
+- `OPL/CollegeOfIdaho/lineAnswer.pl` (not used in OPL/Contrib)
+- `OPL/CollegeOfIdaho/listAnswer.pl` (not used in OPL/Contrib)
+- `OPL/CollegeOfIdaho/parallelAnswer.pl` (not used in OPL/Contrib)
+- `OPL/CollegeOfIdaho/parserImplicitEquation.pl` (already in PG/parsers)
+- `OPL/CollegeOfIdaho/parserTables.pl` (not used in OPL/Contrib)
+- `OPL/CollegeOfIdaho/PGstandard.pl` (already in PG/core)
+- `OPL/CollegeOfIdaho/PGunion.pl` (use union version)
+- `OPL/CollegeOfIdaho/piecewiseFunctions.pl` (not used in OPL/Contrib)
+- `OPL/CollegeOfIdaho/planeAnswer.pl` (not used in OPL/Contrib)
+- `OPL/CollegeOfIdaho/pointAnswer.pl` (not used in OPL/Contrib)
+- `OPL/CollegeOfIdaho/unionAnswer.pl` (not used in OPL/Contrib)
+- `OPL/CollegeOfIdaho/variableAnswer.pl` (not used in OPL/Contrib)
+- `OPL/CollegeOfIdaho/vectorAnswer.pl` (not used in OPL/Contrib)
+- `OPL/CollegeOfIdaho/vectorUtils.pl` (not used in OPL/Contrib)
+- `OPL/CollegeOfIdaho/weightedGrader.pl` (already in PG/core/weightedGrader)
+
+### FortLewis not moved
+
+- `OPL/FortLewis/JavaView.pl` (not in OPL/Contrib)
+- `OPL/FortLewis/JavaViewRectangularPlot3D.pl` (not in OPL/Contrib)
+
+### PCC not moved
+
+- `OPL/PCC/contextAssignmentForm.pl` (no problems in OPL/Contrib)
+- `OPL/PCC/contextInequalitySetBuilder.pl` (newer copy in `PG/macros/contexts`)
+- `OPL/PCC/contextLimitedFactor.pl` (newer copy in `PG/macros/contexts`)
+- `OPL/PCC/contextLimitedRadical.pl` (newer copy in `PG/macros/contexts`)
+- `OPL/PCC/contextPercent.pl` (newer copy in `PG/macros/contexts`)
+- `OPL/PCC/parserRoot.pl` (newer copy in `PG/macros/parsers`)
+
+### Union not moved
+
+- `OPL/Union/answerUtils.pl` (not used in OPL/Contrib)
+- `OPL/Union/courseHeaders.pl` (not used in OPL/Contrib)
+- `OPL/Union/piecewiseFunctions.pl` (not used in OPL/Contrib)
+- `OPL/Union/unorderedAnswer.pl` (used CollegeOfIdaho version, newer)
+
+## problems to check
+
+- `Library/UVA-Stew5e/setUVA-Stew5e-C03S01-DerivPolyExps/3-1-37_mo.pg`
+- `Library/UCSB/Stewart5_3_7/Stewart5_3_7_39.pg`
+- `Library/MC/PreAlgebra/setPreAlgebraC03S03/MeanMedianMode01.pg`
+- `Contrib/AlfredUniv/anton8e/chapter10/10.5/prob9.pg`
+- `Contrib/BrockPhysics/College_Physics_Urone/15.Thermodynamics/Carnots_Perfect_Heat_Engine_The_Second_Law_of_Thermodynamics_Restated/NU_U17-15-04-007.pg`
+- `Library/CollegeOfIdaho/setAlgebra_04_03_AbsoluteValue/43IntAlg_33_AbsValGraph.pg` (error)
+- `Library/CollegeOfIdaho/setAlgebra_04_03_AbsoluteValue/43IntAlg_34_AbsValGraph.pg` (error)
+- `Library/FortLewis/Authoring/Templates/DiffCalcMV/Graph3DCylindrical1/Graph3DCylindrical1.pg` (nothing is viewed)
+- `Library/FortLewis/Calc3/18-2-Line-integrals-parametrized/HGM5-18-2-19-Line-integrals-parametrized/HGM5-18-2-19-Line-integrals-parametrized.pg` (missing png file)
+- `Library/LaTech/opes_Thermodynamics/J/J-01-Brayton01.pg` (gives a warning)
+- `Contrib/Mizzou/Algebra/eqns_abs_value/abs_val_eqn_10.pg` (missing fracListChecker.pl macro)
+- `Library/UMass-Amherst/Abstract-Algebra/PS-Permutations/Permutations2.pg` (warnings due to alegebraMacros.pl)
+- `Contrib/PCC/BasicAlgebra/Factoring/factoring220.pg` (warnings about PCCfactor.pl)
+- `Contrib/PCC/BasicAlgebra/SolveLinearEquations/LiteralEquation60.pg` (warnings about SolveLinearEquationPCC.pl)
+- `Contrib/PCC/BasicAlgebra/SystemsOfLinearEquations/SystemOfEquations20.pg` (warnings about SystemsOfLinearEquationsProblemPCC.pl)
+- `Library/PCC/BasicAlgebra/ComplexNumber/complexSolutions15.pg` (warnings about contextLimitedRadicalComplex.pl)
+- `Contrib/CUNY/CityTech/CollegeAlgebra_Trig/RationalExponents/combine-rational-exponents-multiply.pg` (warning about contextRationalExponent.pl)
+- `Contrib/PCC/CollegeAlgebra/FunctionBasics/FunctionTables20.pg` (warnings about PCCmacros.pl)
+- `Library/Union/setMVfunctions/system-f2.pg` (warning about uninitialized value)
+
+## Macros fixed
+
+- `math/algebraMacros.pl` (removed all code, already in `customizeLaTeX.pl`)
+- `parsers/parserUtils.pl` (removed loadMacros call due to errors. )
+- `contexts/contextFiniteSolutionSets.pl` (fixed warnings)
+- `contexts/contextLimitedRadicalComplex.pl` (fixed warnings)
+- `contexts/contextRationalExponent.pl` (fixed warnings)
+- `math/interpMacros.pl` (fixed warnings)
+- `math/PCCfactor.pl` (fixed warnings)
+- `math/SolveLinearEquationPCC.pl` (fixed warnings)
+- `math/SystemsOfLinearEquationsProblemPCC.pl` (fixed warnings)
+
+## Todo
+
+- remove `BrockPhysicsMacros.pl` doesn't do anything.
+- remove `parserUtils.pl` and refactor the two problems that depend on it. Seems like functionality is elsewhere.
+- remove `answers/answerDiscussion.pl` (not used in OPL/Contrib)
+- Remove `contexts/contextPeriodic.pl`. Labelled as deprecated. No problems in OPL/Contrib.
+- only place `answerUtils.pl` is used is inside `unorderedAnswer.pl` (combine? refactor?)
+- What is the `fracListChecker.pl` macro? In a number of `Contrib/Mizzou` problems.
+- The following are not in OPL/Contrib:
+ - `contextMatrixExtras.pl`
+ - `contextOrdering.pl`
+ - `contextPartition.pl`
+ - `contextPermutation.pl`
+ - `contextPermutationUBC.pl`
+ - `contextReaction.pl`
+ - `contextAlternativeDecimal.pl`
+ - `PGcomplexmacros2.pl`
+ - `parserFormulaAnyVar.pl`
+ - `parserLinearInequality.pl`
+ - `parserParametricPlane.pl`
diff --git a/macros/answers/AnswerFormatHelp.pl b/macros/answers/AnswerFormatHelp.pl
new file mode 100644
index 0000000000..504b8f6ce9
--- /dev/null
+++ b/macros/answers/AnswerFormatHelp.pl
@@ -0,0 +1,868 @@
+
+=head1 NAME
+
+AnswerFormatHelp.pl
+
+=head1 SYNOPSIS
+
+Creates links for students to help documentation on formatting
+answers and allows for custom help links.
+
+=head1 DESCRIPTION
+
+There are 16 predefined help links: angles, decimals, equations,
+exponents, formulas, fractions, inequalities, intervals, limits,
+logarithms, matrices, numbers, points, syntax, units, vectors.
+
+Usage:
+
+ DOCUMENT();
+ loadMacros("PGstandard.pl","AnswerFormatHelp.pl",);
+ TEXT(beginproblem());
+ BEGIN_TEXT
+ \{ ans_rule(20) \}
+ \{ AnswerFormatHelp("formulas") \} $PAR
+ \{ ans_rule(20) \}
+ \{ AnswerFormatHelp("equations","help entering equations") \} $PAR
+ END_TEXT
+ ENDDOCUMENT();
+
+
+The first example use defaults and displays the help link right next to
+the answer blank, which is recommended. The second example customizes
+the link text displayed to the student, but the actual help document
+is unaffected.
+
+=head1 AUTHOR
+
+Paul Pearson, Hope College, Department of Mathematics
+
+=cut
+
+###########################
+
+###########################
+# Site administration
+#
+# The only thing you may need to modify is the value
+# of $helpurl near the end of this file.
+#
+###########################
+# Usage
+#
+# DOCUMENT();
+# loadMacros("PGstandard.pl","AnswerFormatHelp.pl",);
+# TEXT(beginproblem());
+# BEGIN_TEXT
+# \{ AnswerFormatHelp("formulas") \} $PAR
+# \{ AnswerFormatHelp("equations","help entering equations") \} $PAR
+# \{ AnswerFormatHelp("formulas","help (formulas)","http://webwork.someschool.edu/dir/subdir/") \}
+# END_TEXT
+# ENDDOCUMENT();
+#
+# First example: use defaults.
+#
+# Second example: use customized text displayed to the student
+# as the html link.
+#
+# Third example: additionally points to a particular URL where
+# html help files are located. The URL must end with a forward slash.
+#
+# The third method is not recommended, as a universal, site-wide,
+# or course-wide solution obtained by modifying the value of
+# $helpdir in AnswerFormatHelp.pl is preferable to setting the
+# URL in every individual PG file manually.
+#
+###########################
+
+our (
+ $syntaxHelpExists, $angleHelpExists, $decimalHelpExists, $equationHelpExists,
+ $exponentHelpExists, $formulaHelpExists, $fractionHelpExists, $inequalitiesHelpExists,
+ $intervalHelpExists, $limitsHelpExists, $logarithmsHelpExists, $numberHelpExists,
+ $pointsHelpExists, $unitsHelpExists, $vectorsHelpExists,
+) = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,);
+
+sub _AnswerFormatHelp_init { }; # don't reload this file
+
+sub AnswerFormatHelp {
+
+ #
+ # Define some local variables
+ #
+ my $helptype = shift;
+ my $customstring = shift;
+
+ my $helpstring = "";
+
+ # If producing PTX output, omit any formatting help.
+ # (PTX output is used to produce non-interactive presentations of a problem,
+ # so there is no need to give syntax formatting help.)
+ if ($main::displayMode eq "PTX") {
+ return;
+ }
+
+####################################
+ # Angles
+
+ if ($helptype =~ m/angle/i) {
+
+ if ($angleHelpExists != 1) {
+
+ HEADER_TEXT(<
+
+
+END_HEADER_TEXT
+
+ $angleHelpExists = 1;
+ }
+
+ if (!$customstring) { $helpstring = "help (angles)"; }
+ else { $helpstring = $customstring; }
+
+ if ($main::displayMode ne "TeX") {
+ return htmlLink("#", "$helpstring", "onClick='return openhelpAngles()';");
+ } else {
+ return $helpstring;
+ }
+
+####################################
+ # Decimals
+
+ } elsif ($helptype =~ m/decimal/i) {
+
+ if ($decimalHelpExists != 1) {
+
+ HEADER_TEXT(<
+
+
+END_HEADER_TEXT
+
+ $decimalHelpExists = 1;
+ }
+
+ if (!$customstring) { $helpstring = "help (decimals)"; }
+ else { $helpstring = $customstring; }
+
+ if ($main::displayMode ne "TeX") {
+ return htmlLink("#", "$helpstring", "onClick='return openhelpDecimals()';");
+ } else {
+ return $helpstring;
+ }
+
+#########################################
+ # Equations
+
+ } elsif ($helptype =~ m/equation/i) {
+
+ if ($equationHelpExists != 1) {
+
+ HEADER_TEXT(<
+
+
+END_HEADER_TEXT
+
+ $equationHelpExists = 1;
+ }
+
+ if (!$customstring) { $helpstring = "help (equations)"; }
+ else { $helpstring = $customstring; }
+
+ if ($main::displayMode ne "TeX") {
+ return htmlLink("#", "$helpstring", "onClick='return openhelpEquations()';");
+ } else {
+ return $helpstring;
+ }
+
+###############################################
+ # Exponents
+
+ } elsif ($helptype =~ m/exponent/i) {
+
+ if ($exponentHelpExists != 1) {
+
+ HEADER_TEXT(<
+
+
+END_HEADER_TEXT
+
+ $exponentHelpExists = 1;
+ }
+
+ if (!$customstring) { $helpstring = "help (exponents)"; }
+ else { $helpstring = $customstring; }
+
+ if ($main::displayMode ne "TeX") {
+ return htmlLink("#", "$helpstring", "onClick='return openhelpExponents()';");
+ } else {
+ return $helpstring;
+ }
+
+#######################################
+ # Formulas
+
+ } elsif ($helptype =~ m/formula/i) {
+
+ my $local1UseBaseTenLogHelp = Context()->flags->get("useBaseTenLog");
+ my $local2UseBaseTenLogHelp = main::Context()->flags->get("useBaseTenLog");
+ my $globalUseBaseTenLogHelp = $useBaseTenLog;
+
+ if ($local1UseBaseTenLogHelp == 1 || $local2UseBaseTenLogHelp == 1 || $globalUseBaseTenLogHelp == 1) {
+ $useBaseTenLogHelpString =
+ "Entering logarithms: In this question, use ln(x) for natural log, and log(x), logten(x) or log10(x) for the base 10 logarithm. Enter log base b as ln(x)/ln(b) ";
+ } else {
+ $useBaseTenLogHelpString =
+ "Entering logarithms: Caution: In this question, use ln(x) or log(x) for natural log, and logten(x) or log10(x) for the base 10 logarithm. Enter log base b as ln(x)/ln(b) ";
+ }
+
+ if ($formulaHelpExists != 1) {
+
+ HEADER_TEXT(<
+
+
+END_HEADER_TEXT
+
+ $formulaHelpExists = 1;
+ }
+
+ if (!$customstring) { $helpstring = "help (formulas)"; }
+ else { $helpstring = $customstring; }
+
+ if ($main::displayMode ne "TeX") {
+ return htmlLink("#", "$helpstring", "onClick='return openhelpFormulas()';");
+ } else {
+ return $helpstring;
+ }
+
+#########################################
+ # Fractions
+
+ } elsif ($helptype =~ m/fraction/i) {
+
+ if ($fractionHelpExists != 1) {
+
+ HEADER_TEXT(<
+
+
+END_HEADER_TEXT
+
+ $fractionHelpExists = 1;
+ }
+
+ if (!$customstring) { $helpstring = "help (fractions)"; }
+ else { $helpstring = $customstring; }
+
+ if ($main::displayMode ne "TeX") {
+ return htmlLink("#", "$helpstring", "onClick='return openhelpFractions()';");
+ } else {
+ return $helpstring;
+ }
+
+###########################################
+ # Inequalities
+
+ } elsif ($helptype =~ m/inequalit/i) {
+
+ if ($inequalitiesHelpExists != 1) {
+
+ HEADER_TEXT(<
+
+
+END_HEADER_TEXT
+
+ $inequalitiesHelpExists = 1;
+ }
+
+ if (!$customstring) { $helpstring = "help (inequalities)"; }
+ else { $helpstring = $customstring; }
+
+ if ($main::displayMode ne "TeX") {
+ return htmlLink("#", "$helpstring", "onClick='return openhelpInequalities()';");
+ } else {
+ return $helpstring;
+ }
+
+#######################################
+ # Intervals
+
+ } elsif ($helptype =~ m/interval/i) {
+
+ if ($intervalHelpExists != 1) {
+
+ HEADER_TEXT(<
+
+
+END_HEADER_TEXT
+
+ $intervalHelpExists = 1;
+ }
+
+ if (!$customstring) { $helpstring = "help (intervals)"; }
+ else { $helpstring = $customstring; }
+
+ if ($main::displayMode ne "TeX") {
+ return htmlLink("#", "$helpstring", "onClick='return openhelpIntervals()';");
+ } else {
+ return $helpstring;
+ }
+
+##############################################
+ # Limits
+
+ } elsif ($helptype =~ m/limit/i) {
+
+ if ($limitsHelpExists != 1) {
+
+ HEADER_TEXT(<
+
+
+END_HEADER_TEXT
+
+ $limitsHelpExists = 1;
+ }
+
+ if (!$customstring) { $helpstring = "help (limits)"; }
+ else { $helpstring = $customstring; }
+
+ if ($main::displayMode ne "TeX") {
+ return htmlLink("#", "$helpstring", "onClick='return openhelpLimits()';");
+ } else {
+ return $helpstring;
+ }
+
+###############################################
+ # Logarithms
+
+ } elsif ($helptype =~ m/log/i) {
+
+ my $local1UseBaseTenLogHelp = Context()->flags->get("useBaseTenLog");
+ my $local2UseBaseTenLogHelp = main::Context()->flags->get("useBaseTenLog");
+ my $globalUseBaseTenLogHelp = $useBaseTenLog;
+
+ if ($local1UseBaseTenLogHelp == 1 || $local2UseBaseTenLogHelp == 1 || $globalUseBaseTenLogHelp == 1) {
+ $naturalLogHelpString =
+ "Entering natural logarithm: In this question, use ln(x) ";
+ $basetenLogHelpString =
+ "Entering base 10 logarithm: In this question, use log(x), log10(x), or logten(x). ";
+ } else {
+ $naturalLogHelpString =
+ "Caution: In this question log(x) is the same as ln(x)Entering natural logarithm: In this question, use ln(x) or log(x) ";
+ $basetenLogHelpString =
+ "Entering base 10 logarithm: In this question, use log10(x) or logten(x). ";
+ }
+
+ if ($logarithmsHelpExists != 1) {
+
+ HEADER_TEXT(<
+
+
+END_HEADER_TEXT
+
+ $logarithmsHelpExists = 1;
+ }
+
+ if (!$customstring) { $helpstring = "help (logarithms)"; }
+ else { $helpstring = $customstring; }
+
+ if ($main::displayMode ne "TeX") {
+ return htmlLink("#", "$helpstring", "onClick='return openhelpLogarithms()';");
+ } else {
+ return $helpstring;
+ }
+
+###########################################
+ # Matrices
+
+ } elsif ($helptype =~ m/matri/i) {
+
+ if ($matricesHelpExists != 1) {
+
+ HEADER_TEXT(<
+
+
+END_HEADER_TEXT
+
+ $matricesHelpExists = 1;
+ }
+
+ if (!$customstring) { $helpstring = "help (matrices)"; }
+ else { $helpstring = $customstring; }
+
+ if ($main::displayMode ne "TeX") {
+ return htmlLink("#", "$helpstring", "onClick='return openhelpMatrices()';");
+ } else {
+ return $helpstring;
+ }
+
+##########################################
+ # Numbers
+
+ } elsif ($helptype =~ m/number/i) {
+
+ if ($numberHelpExists != 1) {
+
+ HEADER_TEXT(<
+
+
+END_HEADER_TEXT
+
+ $numberHelpExists = 1;
+ }
+
+ if (!$customstring) { $helpstring = "help (numbers)"; }
+ else { $helpstring = $customstring; }
+
+ if ($main::displayMode ne "TeX") {
+ return htmlLink("#", "$helpstring", "onClick='return openhelpNumbers()';");
+ } else {
+ return $helpstring;
+ }
+
+#######################################
+ # Points
+
+ } elsif ($helptype =~ m/point/i) {
+
+ if ($pointsHelpExists != 1) {
+
+ HEADER_TEXT(<
+
+
+END_HEADER_TEXT
+
+ $pointsHelpExists = 1;
+ }
+
+ if (!$customstring) { $helpstring = "help (points)"; }
+ else { $helpstring = $customstring; }
+
+ if ($main::displayMode ne "TeX") {
+ return htmlLink("#", "$helpstring", "onClick='return openhelpPoints()';");
+ } else {
+ return $helpstring;
+ }
+
+####################################
+ # Units
+
+ } elsif ($helptype =~ m/unit/i) {
+
+ if ($unitsHelpExists != 1) {
+
+ HEADER_TEXT(<
+
+
+END_HEADER_TEXT
+
+ $unitsHelpExists = 1;
+ }
+
+ if (!$customstring) { $helpstring = "help (units)"; }
+ else { $helpstring = $customstring; }
+
+ if ($main::displayMode ne "TeX") {
+ return htmlLink("#", "$helpstring", "onClick='return openhelpUnits()';");
+ } else {
+ return $helpstring;
+ }
+
+########################################
+ # Vectors
+
+ } elsif ($helptype =~ m/vector/i) {
+
+ if ($vectorsHelpExists != 1) {
+
+ HEADER_TEXT(<
+
+
+END_HEADER_TEXT
+
+ $vectorsHelpExists = 1;
+ }
+
+ if (!$customstring) { $helpstring = "help (vectors)"; }
+ else { $helpstring = $customstring; }
+
+ if ($main::displayMode ne "TeX") {
+ return htmlLink("#", "$helpstring", "onClick='return openhelpVectors()';");
+ } else {
+ return $helpstring;
+ }
+
+#########################################
+ # Syntax (the catch all)
+
+ #} elsif ($helptype =~ m/syntax/i) {
+ } else {
+
+ if ($syntaxHelpExists != 1) {
+
+ HEADER_TEXT(<
+
+
+END_HEADER_TEXT
+
+ $syntaxHelpExists = 1;
+ }
+
+ if (!$customstring) { $helpstring = "help (syntax)"; }
+ else { $helpstring = $customstring; }
+
+ if ($main::displayMode ne "TeX") {
+ return htmlLink("#", "$helpstring", "onClick='return openhelpSyntax()';");
+ } else {
+ return $helpstring;
+ }
+
+ }
+
+} # end AnswerFormatHelp
+
+1;
diff --git a/macros/answers/ConditionalHint.pl b/macros/answers/ConditionalHint.pl
new file mode 100644
index 0000000000..441a23fcc3
--- /dev/null
+++ b/macros/answers/ConditionalHint.pl
@@ -0,0 +1,170 @@
+sub _ConditionalHint_init { }; # don't reload this file
+
+=head1 ConditionalHint.pl
+
+################################################################################
+# WeBWorK Online Homework Delivery System
+# Copyright � 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/
+# $CVSHeader$
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of either: (a) the GNU General Public License as published by the
+# Free Software Foundation; either version 2, or (at your option) any later
+# version, or (b) the "Artistic License" which comes with this package.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the
+# Artistic License for more details.
+################################################################################
+
+=head2 NAME
+
+ConditionalHint.pl - Allows a hint to be revealed after a student
+has entered an answer correctly.
+
+=head2 DESCRIPTION
+
+The subroutine ConditionalHint() allows a hint to be revealed
+after a student has entered an answer correctly. It is useful
+for multi-part questions in which a hint for answering one part
+should not be revealed until a previous part has been answered
+correctly.
+
+A subroutine IsAnswerCorrect() that returns 0 or 1 is also
+provided.
+
+=head2 USAGE
+
+=head3 Synopsis of ConditionalHint
+
+loadMacros("ConditionalHint.pl");
+
+$ans = Compute("x^2");
+
+BEGIN_TEXT
+Enter \( x^2 \) \{ ans_rule(20) \}
+\{
+ConditionalHint(
+ ans_name=>$ans,
+ ans_number=>1,
+ html_hint=>"$BR ${BBOLD}Hint:${EBOLD}
+ \( \displaystyle \int x^2 \, dx = \frac{x^3}{3} + C.\) $BR",
+ tex_hint=>'',
+);
+\}
+END_TEXT
+
+ANS( $ans->cmp() );
+
+
+=head3 Complete Working Example of ConditionalHint
+
+DOCUMENT();
+
+loadMacros(
+"PGstandard.pl",
+"MathObjects.pl",
+"ConditionalHint.pl",
+);
+
+TEXT( beginproblem() );
+
+###################################
+# Setup
+
+Context("Numeric");
+
+@answers = ();
+$answers[1] = Compute("x^2");
+$answers[2] = Compute("4^3 / 3");
+
+#$hint = IsAnswerCorrect(ans_name=>$answers[1],ans_number=>1);
+
+$myhint = ConditionalHint(
+ans_name=>$answers[1],
+ans_number=>1,
+html_hint=>"$BR ${BBOLD}Hint:${EBOLD} \( \displaystyle \int x^2 \, dx = \frac{x^3}{3} + C.\) $BR"
+);
+
+
+##################################
+# Main text
+
+Context()->texStrings;
+BEGIN_TEXT
+(a) Enter \( x^2 \).
+\{ ans_rule(30) \}
+$BR
+$BR
+(b) When you answer part (a) correctly, a hint will appear here with an integral formula.
+$BR
+$myhint
+$BR
+\( \displaystyle \int_0^4 \frac{x^3}{3} \, dx = \)
+\{ans_rule(20)\}
+END_TEXT
+Context()->normalStrings;
+
+
+##################################
+# Answer evaluation
+
+$showPartialCorrectAnswers = 1;
+
+ANS( $answers[1]->cmp() );
+ANS( $answers[2]->cmp() );
+
+COMMENT('When the first answer is correct, a hint appears with an integral formula (using ConditionalHint.pl).');
+
+ENDDOCUMENT();
+
+=head2 AUTHOR
+
+Paul Pearson
+
+=cut
+
+sub ConditionalHint {
+
+ my %options = (
+ ans_name => '',
+ ans_number => '',
+ html_hint => '',
+ tex_hint => '',
+ @_
+ );
+
+ my $showhint = $options{ans_name}->cmp()->evaluate($inputs_ref->{ ANS_NUM_TO_NAME($options{ans_number}) })->{score};
+
+ my $hint;
+
+ if ($showhint == 1) {
+ $hint = MODES(HTML => $options{html_hint}, TeX => $options{tex_hint});
+ } else {
+ $hint = '';
+ }
+
+ return $hint;
+
+}
+
+sub IsAnswerCorrect {
+
+ my %options = (
+ ans_name => '',
+ ans_number => '',
+ @_
+ );
+
+ # my $name_of_correct_answer = shift;
+ # my $answer_rule_number = shift;
+
+ # return $name_of_correct_answer->cmp()->evaluate($inputs_ref->{ANS_NUM_TO_NAME($answer_rule_number)})->{score};
+
+ return $options{ans_name}->cmp()->evaluate($inputs_ref->{ ANS_NUM_TO_NAME($options{ans_number}) })->{score};
+
+}
+
+1;
+
diff --git a/macros/answers/Generic.pl b/macros/answers/Generic.pl
new file mode 100644
index 0000000000..6e52d12125
--- /dev/null
+++ b/macros/answers/Generic.pl
@@ -0,0 +1,187 @@
+#R. Byerly, Texas Tech University, 2005
+
+sub _Generic { } #Don't reload this file.
+
+#Documentation:
+
+=head1 Generic Answer Checker
+
+(Based on David Cervone's vector_cmp. See
+doc/parser/extensions/8-answer.pg under the webwork2 directory.)
+Place in macros directory and load this file (Generic.pl) using the
+loadMacros command. (To just copy it into a pg file, replace
+occurences of "\&" by "~~&".)
+
+ Usage:
+
+ ANS( generic_cmp(, ) );
+where is a parser object or syntactically correct string
+for a parser object.
+
+Mandatory arguments:
+
+ type =>
+where is a recognized parser type (e.g., Number, Point, Vector,
+Matrix, etc.)
+
+ checker =>
+where is a reference to a subroutine that will be passed (in
+order) the parsed (and, if possible, evaluated) student answer, the
+parsed professor's answer, and a reference to the answer hash. (The
+last is so that it can return error messages if desired.) In simple
+evaluators, the last two are typically not used.
+
+Optional arguments:
+
+ correct_ans =>
+where is a string that will show up as the correct
+answer when solutions are available.
+
+ variables_allowed => 0 or 1
+(default 0 (false). Determines whether student's answer is allowed to
+contain variables. In this case the checker must take care of
+evaluating it.)
+
+ length => n
+where n is the number of elements in an expected answer of type list,
+vector, or points. Returns error message to student if answer of wrong
+length is entered.
+
+
+####################Example:##########################
+DOCUMENT(); # This should be the first executable line in the problem.
+
+loadMacros(
+"PG.pl",
+"PGbasicmacros.pl",
+"PGanswermacros.pl",
+"Parser.pl",
+"Generic.pl",
+);
+
+TEXT(&beginproblem);
+
+Context("Vector");
+$A=Vector(1,2,1);
+$B=Vector(1,3,1);
+$C=Vector(1,4,1);
+
+BEGIN_TEXT
+Show that the vectors \(\{$A->TeX\}, \{$B->TeX\}, \{$C->TeX\}\) do
+not span \(R^3\) by giving a vector not in their span:
+\{ans_rule()\}
+END_TEXT
+
+#Easy to get by guessing!
+
+sub check{
+ my $stu=shift();
+ $x1=$stu->extract(1); $x3=$stu->extract(3);
+ $x1 != $x3; #any vectors with different 1st and 3rd coordinates
+}
+
+ANS(generic_cmp("23",type => 'Vector', length => 3, checker => ~~&check));
+
+ENDDOCUMENT(); # This should be the last executable line in the problem.
+
+####################End of Example########################
+
+=cut
+
+#End of Documentation
+
+#From parserUtils.pl:
+sub protectHTML {
+ my $string = shift;
+ $string =~ s/&/\&/g;
+ $string =~ s/\</g;
+ $string =~ s/>/\>/g;
+ $string;
+
+}
+
+sub generic_cmp {
+ my $v = shift;
+ my %opts = @_;
+ die "generic_cmp requires an argument as answer" unless defined $v;
+ die "generic_cmp requires a checker" unless defined $opts{'checker'};
+ die "generic_cmp requires a type" unless defined $opts{'type'};
+ $v = Formula($v);
+
+ $opts{'correct_ans'} = $v->string if (!defined($opts{'correct_ans'}));
+ $opts{'variables_allowed'} = 0 if (!defined($opts{'variables_allowed'}));
+ $opts{ $opts{'type'} } = $v;
+
+ my $ans = new AnswerEvaluator;
+
+ $ans->ans_hash((%opts));
+ # $ans->install_evaluator(~~&generic_cmp_check);
+ $ans->install_evaluator(\&generic_cmp_check);
+ return $ans;
+}
+
+sub generic_cmp_check {
+ my $ans = shift;
+ my $type = $ans->{type};
+ my $v = $ans->{$type};
+ $ans->score(0); # assume failure
+ my $f = Parser::Formula($ans->{student_ans});
+ my @vars = (keys(%{ $f->{variables} }));
+ if (@vars == 0) {
+ $V = Parser::Evaluate($f);
+ } elsif ($ans->{variables_allowed}) {
+ $V = $f; #if there are variables in the students answer, let the checker have it.
+ } else {
+ $ans->{ans_message} = $ans->{error_message} =
+ "Your answer contains variables " . join(",", @vars);
+ return $ans;
+ }
+ if (defined $V) {
+ $V = Formula($V) unless Value::isValue($V); # make sure we can call Value methods
+ $ans->{preview_latex_string} = $V->TeX;
+ $ans->{preview_text_string} = $V->string;
+ $ans->{student_ans} = $V->string;
+ #Some checks:
+ if (($type eq 'List') && !($V->type eq 'List')) {
+ $V = List($V); #promote single element to list
+ }
+
+ if (defined $ans->{length}) {
+ if ($ans->{length} != $V->length) {
+ $ans->{ans_message} = $ans->{error_message} = "Wrong number of entries in answer";
+ return $ans;
+ }
+ }
+
+ if (($V->type eq $type)) {
+ $ans->score(1) if ($ans->{checker}->($V, $v, $ans));
+
+ } else {
+ $ans->{ans_message} = $ans->{error_message} =
+ "Your answer doesn't seem to be a $type (it looks like " . Value::showClass($V) . ")"
+ unless $inputs_ref->{previewAnswers};
+ }
+ } else {
+ #
+ # Student answer evaluation failed.
+ # Report the error, with formatting, if possible.
+ #
+ my $context = Context();
+ my $message = $context->{error}{message};
+ if ($context->{error}{pos}) {
+ my $string = $context->{error}{string};
+ my ($s, $e) = @{ $context->{error}{pos} };
+ $message =~ s/; see.*//; # remove the position from the message
+ $ans->{student_ans} =
+ protectHTML(substr($string, 0, $s))
+ . ''
+ . protectHTML(substr($string, $s, $e - $s))
+ . ' '
+ . protectHTML(substr($string, $e));
+ }
+ $ans->{ans_message} = $ans->{error_message} = $message;
+ }
+ return $ans;
+}
+
+1;
diff --git a/macros/PGasu.pl b/macros/answers/PGasu.pl
similarity index 99%
rename from macros/PGasu.pl
rename to macros/answers/PGasu.pl
index 6f23dafa50..2862621106 100644
--- a/macros/PGasu.pl
+++ b/macros/answers/PGasu.pl
@@ -17,7 +17,7 @@ =head3 auto_right()
=pod
- Usage: ANS(auto_right());
+ Usage: ANS(auto_right());
or
ANS(auto_right("this answer can be left blank"));
@@ -90,7 +90,7 @@ =head3 must_include()
=pod
Wrapper for other answer evaluators. It insists that a string is part of
-the answer to be marked right.
+the answer to be marked right.
=cut
@@ -116,8 +116,8 @@ =head3 no_trig_fun()
This is useful if you want students to report the value of sin(pi/4),
but you don't want to allow "sin(pi/4)" as the answer.
-A similar effect can be accomplished with Contexts() by undefining
-the trig functions.
+A similar effect can be accomplished with Contexts() by undefining
+the trig functions.
See http://webwork.maa.org/wiki/Modifying_contexts_%28advanced%29#.282.29_Functions
@@ -200,7 +200,7 @@ =head3 must_have_filter()
string in it. This can be used to screen answers where you want them
in a particular form (e.g., if you allow most functions, but not trig
functions in the answer, or if the answer must include some string).
-
+
First argument is the string to have, or not have
Second argument is optional, and tells us whether yes or no
Third argument is the error message to produce (if any).
@@ -208,8 +208,8 @@ =head3 must_have_filter()
When using this filter directly, you also need to install catch_errors_filter
as a post filter.
-A similar effect can be accomplished with Contexts() by undefining
-the trig functions.
+A similar effect can be accomplished with Contexts() by undefining
+the trig functions.
See http://webwork.maa.org/wiki/Modifying_contexts_%28advanced%29
=cut
diff --git a/macros/PGfunctionevaluators.pl b/macros/answers/PGfunctionevaluators.pl
similarity index 100%
rename from macros/PGfunctionevaluators.pl
rename to macros/answers/PGfunctionevaluators.pl
diff --git a/macros/PGmiscevaluators.pl b/macros/answers/PGmiscevaluators.pl
similarity index 100%
rename from macros/PGmiscevaluators.pl
rename to macros/answers/PGmiscevaluators.pl
diff --git a/macros/PGstringevaluators.pl b/macros/answers/PGstringevaluators.pl
similarity index 100%
rename from macros/PGstringevaluators.pl
rename to macros/answers/PGstringevaluators.pl
diff --git a/macros/PGtextevaluators.pl b/macros/answers/PGtextevaluators.pl
similarity index 100%
rename from macros/PGtextevaluators.pl
rename to macros/answers/PGtextevaluators.pl
diff --git a/macros/answerComposition.pl b/macros/answers/answerComposition.pl
similarity index 100%
rename from macros/answerComposition.pl
rename to macros/answers/answerComposition.pl
diff --git a/macros/answerCustom.pl b/macros/answers/answerCustom.pl
similarity index 100%
rename from macros/answerCustom.pl
rename to macros/answers/answerCustom.pl
diff --git a/macros/answerDiscussion.pl b/macros/answers/answerDiscussion.pl
similarity index 100%
rename from macros/answerDiscussion.pl
rename to macros/answers/answerDiscussion.pl
diff --git a/macros/answerHints.pl b/macros/answers/answerHints.pl
similarity index 100%
rename from macros/answerHints.pl
rename to macros/answers/answerHints.pl
diff --git a/macros/answers/answerUtils.pl b/macros/answers/answerUtils.pl
new file mode 100644
index 0000000000..4d77ccc1e5
--- /dev/null
+++ b/macros/answers/answerUtils.pl
@@ -0,0 +1,105 @@
+##########################################################################
+#
+# Utility routines for answer checkers
+#
+#########################################################################
+
+sub _answerUtils_init { }; # don't load again
+
+loadMacros("unionUtils.pl");
+
+#
+# Evaluate an answer checker with a given student answer.
+# (works with old- and new-style answer checkers)
+#
+sub evaluateAnswer {
+ my ($ans_evaluator, $student_answer, $skipblanks) = @_;
+ return if (!$ans_evaluator);
+ return if ($skipblanks && trimString($student_answer) eq '');
+ clearEvaluator($ans_evaluator);
+ if (ref($ans_evaluator) eq 'AnswerEvaluator') {
+ $ans_evaluator->{rh_ans}{ans_label} = "" unless defined($ans_evaluator->{rh_ans}{ans_label});
+ return ($ans_evaluator->evaluate($student_answer));
+ } elsif (ref($ans_evaluator) eq 'CODE') {
+ return (&$ans_evaluator($student_answer));
+ } else {
+ warn "There is a problem using the answer evaluator";
+ }
+}
+
+#
+# Call an answer evaluator (works for new- and old-style checkers)
+#
+sub isCorrectAnswer {
+ my $cmp = shift;
+ my $correct = $cmp->{rh_ans}->{correct_ans};
+ my $hash = evaluateAnswer($cmp, @_, 1);
+ $cmp->{rh_ans}->{correct_ans} = $correct;
+ return (0) unless defined($hash);
+ return ($hash->{score} == 1);
+}
+
+#
+# Clear the error condition for an answer evaluator
+#
+sub clearEvaluator {
+ my $hash = hashFor(shift);
+ return (0) unless defined($hash);
+ $hash->setKeys(
+ ans_message => '',
+ preview_text_string => '',
+ preview_latex_string => '',
+ original_student_ans => '',
+ student_ans => '',
+ );
+ $hash->score(0);
+}
+
+#
+# Get the answer hash for a given evaluator
+#
+sub hashFor {
+ my ($ans_evaluator) = @_;
+ if (ref($ans_evaluator) eq 'AnswerEvaluator') { return $ans_evaluator->rh_ans }
+ elsif (ref($ans_evaluator) eq 'CODE') { return $ans_evaluator }
+ else { warn "There is a problem using the answer evaluator" }
+}
+
+#
+# Make error messages returned by answer checkers prettier
+#
+sub IndentError {
+ my $error = trimString(shift);
+ my $n = shift;
+ $n = 4 unless defined($n);
+ my $indent = '';
+ $indent .= ' ' while ($n--);
+ $error =~ s/ (There is a syntax error)/\n$1/g;
+ $error =~ s/ Your/\nYour/g;
+ $error =~ s/\n */\n$indent/g;
+ return $indent . $error;
+}
+
+#
+# Return the string or a blank string (if it was not defined)
+#
+sub StringOrBlank {
+ my $s = shift;
+ my $default = shift;
+ $default = '' unless defined($default);
+ $s = $default unless defined($s);
+ return $s;
+}
+
+#
+# Check for preview mode
+#
+sub isPreviewMode {
+ # for WW1
+ return ($inputs_ref->{action} =~ m/^Preview/)
+ if (defined($inputs_ref) && defined($inputs_ref->{action}));
+ # for WW2
+ return defined($inputs_ref) && defined($inputs_ref->{previewAnswers});
+}
+
+1;
diff --git a/macros/answerVariableList.pl b/macros/answers/answerVariableList.pl
similarity index 100%
rename from macros/answerVariableList.pl
rename to macros/answers/answerVariableList.pl
diff --git a/macros/extraAnswerEvaluators.pl b/macros/answers/extraAnswerEvaluators.pl
similarity index 100%
rename from macros/extraAnswerEvaluators.pl
rename to macros/answers/extraAnswerEvaluators.pl
diff --git a/macros/problemPreserveAnswers.pl b/macros/answers/problemPreserveAnswers.pl
similarity index 100%
rename from macros/problemPreserveAnswers.pl
rename to macros/answers/problemPreserveAnswers.pl
diff --git a/macros/answers/unorderedAnswer.pl b/macros/answers/unorderedAnswer.pl
new file mode 100644
index 0000000000..deb3ec645c
--- /dev/null
+++ b/macros/answers/unorderedAnswer.pl
@@ -0,0 +1,170 @@
+##########################################################################
+##########################################################################
+##
+## Routines for groups of answer blanks where the user can enter
+## answers in any order in the blanks.
+##
+
+loadMacros("answerUtils.pl");
+
+sub _unorderedAnswer_init { }; # don't reload this file
+
+##########################################################################
+#
+# Collect a group of answer checkers for use with answers that can be given
+# in any order. If N answer checkers are given, then the last N answer
+# rules will be used. It is beter to use named rules and UNORDERED_NAMED_ANS
+# below. Otherwise, be sure to use UNORDERED_ANS right after the answer
+# rules for the answers you want to compare.
+#
+# Format:
+#
+# UNORDERED_ANS(checker1, checker2, ...);
+#
+# Example:
+#
+# BEGIN_TEXT
+# The function \(f(x) = \frac{1}{x^2-$a}\) is defined except
+# for \(x =\) \{ans_rule(10)\} and \(x =\) \{ans_rule(10)\}.
+# END_TEXT
+#
+# UNORDERED_ANS(std_num_cmp(sqrt($a)), std_num_cmp(-sqrt($a)));
+#
+# (the student can enter the solutions in either order.)
+#
+sub UNORDERED_ANS {
+ my @cmp = @_;
+ my @params = ();
+ my $i;
+ my $n = scalar(@cmp);
+ #
+ # The best thing would be to use the size of @PG_ANSWERS in place of
+ # $main::ans_rule_count, but we don't have access to that
+ #
+ foreach $i (1 .. $n) { push(@params, ANS_NUM_TO_NAME($i + main::ans_rule_count() - $n), $cmp[ $i - 1 ]) }
+ my @results = unordered_answer_list(@params);
+ while (scalar(@results) > 0) { shift(@results), ANS(shift(@results)) }
+}
+
+##########################################################################
+#
+# Collect a group of answer checkers for use with named answers that
+# can be given in any order.
+#
+# Format:
+#
+# UNORDERED_NAMED_ANS(name1 => checker1, name2 => checker2, ...);
+#
+# Example:
+#
+# BEGIN_TEXT
+# The function \(f(x) = \frac{1}{x^2-$a}\) is defined except
+# for \(x =\) \{NAMED_ANS_RULE(A1,10)\}
+# and \(x =\) \{NAMED_ANS_RULE(A2,10)\}.
+# END_TEXT
+#
+# UNORDERED_NAMED_ANS(
+# A1 => std_num_cmp(sqrt($a)),
+# A2 => std_num_cmp(-sqrt($a))
+# );
+#
+# (the student can enter the solutions in either blank.)
+#
+sub UNORDERED_NAMED_ANS {
+ NAMED_ANS(unordered_answer_list(@_));
+}
+
+##########################################################################
+#
+# Low-level routine for handling unordered collections of answer checkers
+#
+sub unordered_answer_list {
+ my %params = @_;
+ my @args = @_;
+ my (@cmp, @ids);
+ while (scalar(@_) > 0) { push(@ids, shift); push(@cmp, shift) }
+ #
+ # Setup
+ #
+ my ($i, $j, $k);
+ my $n = scalar(@cmp);
+ my @cmpi = (0 .. $n - 1);
+ my @skipped = ();
+ #
+ # Check that the answers exist (otherwise it's our first time through)
+ #
+ foreach $i (@ids) { return (@args) if (!defined($inputs_ref->{$i})) }
+ #
+ # Check each answer against the available answer checkers.
+ # Keep track of the ones that match and that don't.
+ #
+ANSWER: foreach $i (0 .. $n - 1) {
+ $k = 0;
+ foreach $j (@cmpi) {
+ if (isCorrectAnswer($cmp[$j], $inputs_ref->{ $ids[$i] })) {
+ $ans[$i] = $j;
+ splice(@cmpi, $k, 1);
+ next ANSWER;
+ }
+ $k++;
+ }
+ push(@skipped, $i);
+ }
+ #
+ # Check if the unmatched checkers are all blank checkers.
+ # If so, let them report blanks as correct answers.
+ #
+ my $blankOK = 1;
+ foreach $i (@cmpi) {
+ if (ref($cmp[$i]) ne "AnswerEvaluator" || ($cmp[$i]->rh_ans)->{type} ne "blank") { $blankOK = 0; last }
+ }
+ if ($blankOK) {
+ foreach $i (@cmpi) { ($cmp[$i]->rh_ans)->{blankOK} = 1 }
+ }
+ #
+ # Assign the unmatching answers to umatched checkers
+ #
+ foreach $i (0 .. scalar(@skipped) - 1) { $ans[ $skipped[$i] ] = $cmpi[$i] }
+ #
+ # Make the final list of answer checkers in their proper order
+ #
+ my (@list) = ();
+ foreach $i (0 .. $n - 1) { clearEvaluator($cmp[ $ans[$i] ]); push(@list, $ids[$i], $cmp[ $ans[$i] ]) }
+ return (@list);
+}
+
+##################################################
+#
+# AnswerChecker that allows a blank answer in a collection of unordered
+# answer checkers. It will return "correct" for a blank answer only if
+# all the other answers are correct. (The blankOK value is set by
+# unordered_answer_list when this is true.) This lets you ask a question
+# where the number of answers is not known (by the student) ahead of time.
+#
+sub blank_cmp {
+ my %params = @_;
+ $params{debug} = 0 unless defined($params{debug});
+ my $answerEvaluator = new AnswerEvaluator;
+ $answerEvaluator->{debug} = $params{debug};
+ $answerEvaluator->ans_hash(
+ type => "blank",
+ blankOK => 0,
+ correct_ans => '',
+ weight => 0
+ );
+ $answerEvaluator->install_pre_filter('reset'); # remove the blank filter
+ $answerEvaluator->install_evaluator(\&blank_cmp_check, %params);
+ $answerEvaluator->install_post_filter('reset'); # remove the blank filter
+ return $answerEvaluator;
+}
+
+sub blank_cmp_check {
+ my $ans = shift;
+ my %params = @_;
+ $ans->{student_ans} = trimString($ans->{student_ans});
+ if ($ans->{student_ans} eq "") { $ans->score($ans->{blankOK}) }
+ else { $ans->score(0) }
+ return ($ans);
+}
+
+1;
diff --git a/macros/PG_CAPAmacros.pl b/macros/capa/PG_CAPAmacros.pl
similarity index 100%
rename from macros/PG_CAPAmacros.pl
rename to macros/capa/PG_CAPAmacros.pl
diff --git a/macros/StdConst.pg b/macros/capa/StdConst.pg
similarity index 100%
rename from macros/StdConst.pg
rename to macros/capa/StdConst.pg
diff --git a/macros/StdUnits.pg b/macros/capa/StdUnits.pg
similarity index 100%
rename from macros/StdUnits.pg
rename to macros/capa/StdUnits.pg
diff --git a/macros/contextABCD.pl b/macros/contexts/contextABCD.pl
similarity index 100%
rename from macros/contextABCD.pl
rename to macros/contexts/contextABCD.pl
diff --git a/macros/contextAlternateDecimal.pl b/macros/contexts/contextAlternateDecimal.pl
similarity index 100%
rename from macros/contextAlternateDecimal.pl
rename to macros/contexts/contextAlternateDecimal.pl
diff --git a/macros/contextAlternateIntervals.pl b/macros/contexts/contextAlternateIntervals.pl
similarity index 100%
rename from macros/contextAlternateIntervals.pl
rename to macros/contexts/contextAlternateIntervals.pl
diff --git a/macros/contextArbitraryString.pl b/macros/contexts/contextArbitraryString.pl
similarity index 100%
rename from macros/contextArbitraryString.pl
rename to macros/contexts/contextArbitraryString.pl
diff --git a/macros/contextComplexExtras.pl b/macros/contexts/contextComplexExtras.pl
similarity index 100%
rename from macros/contextComplexExtras.pl
rename to macros/contexts/contextComplexExtras.pl
diff --git a/macros/contextComplexJ.pl b/macros/contexts/contextComplexJ.pl
similarity index 100%
rename from macros/contextComplexJ.pl
rename to macros/contexts/contextComplexJ.pl
diff --git a/macros/contextCongruence.pl b/macros/contexts/contextCongruence.pl
similarity index 100%
rename from macros/contextCongruence.pl
rename to macros/contexts/contextCongruence.pl
diff --git a/macros/contextCurrency.pl b/macros/contexts/contextCurrency.pl
similarity index 100%
rename from macros/contextCurrency.pl
rename to macros/contexts/contextCurrency.pl
diff --git a/macros/contexts/contextFiniteSolutionSets.pl b/macros/contexts/contextFiniteSolutionSets.pl
new file mode 100644
index 0000000000..388c5ad10e
--- /dev/null
+++ b/macros/contexts/contextFiniteSolutionSets.pl
@@ -0,0 +1,338 @@
+################################################################################
+# WeBWorK Online Homework Delivery System
+# Copyright © 2000-2018 The WeBWorK Project, http://openwebwork.sf.net/
+# $CVSHeader$
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of either: (a) the GNU General Public License as published by the
+# Free Software Foundation; either version 2, or (at your option) any later
+# version, or (b) the "Artistic License" which comes with this package.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the
+# Artistic License for more details.
+################################################################################
+
+=head1 NAME
+
+contextFiniteSolutionSets.pl - Allows several common input forms for a finite
+solution set.
+
+After setting the context to "FiniteSolutionSets", make an answer like:
+Formula("1,2")
+Formula("1/2,2/3,3/4")
+Formula("sqrt(2),(1+sqrt(2))/3, -(sqrt(2)+3sqrt(5))/6")
+List(Formula("1"))
+
+Note that if it is a singleton, the structure is different.
+
+Then students can enter the answer in any number of ways. Like:
+1,2
+x=1,2
+x=1,x=2
+x=1 or 2
+x=1 or x=2
+{1,2}
+
+The "x" should be the only context variable, and it should be what the student
+needed to solve for.
+
+If the answer is not submitted like {1,2}, and if the flag preferSetNotation is
+set to 1 (which is the default) then there is a message that this is the
+preferred form (but the student still gets credit).
+
+Answers like 2/3 and (1+sqrt(2))/3 need to be in that form, so the problem
+author needs to be careful to not do things like Formula("1/2,$a/$b,3/4")
+where $a and $b have a nontrivial common divisor.
+
+No decimals are allowed.
+
+=head1 DESCRIPTION
+
+=cut
+
+loadMacros(
+ "MathObjects.pl", "bizarroArithmetic.pl", "parserAssignment.pl", "contextFraction.pl",
+ "parserRoot.pl", "PGinfo.pl",
+);
+
+sub _contextFiniteSolutionSets_init {
+ my $context = $main::context{FiniteSolutionSets} = Parser::Context->getCopy("Interval");
+ $context->{name} = "FiniteSolutionSets";
+ Parser::Number::NoDecimals($context);
+ parser::Assignment->Allow($context);
+ $context->flags->set(
+ reduceConstants => 0,
+ reduceConstantFunctions => 0,
+ formatStudentAnswer => "parsed",
+ checkSqrt => 0,
+ checkRoot => 0,
+ useBizarro => 1,
+ setSqrt => exp(1) / main::ln(2),
+ setRoot => exp(2) / main::ln(3),
+ wrongFormMessage => 'Your answer is numerically correct, but not in the expected form',
+ preferSetNotation => 1,
+ #tolerance => 0.00001,
+ );
+ $context->noreduce('(-x)+y', '(-x)-y');
+ $context->operators->set(
+ '+' => { class => 'bizarro::BOP::add', isCommand => 1 }, # override +
+ '-' => { class => 'bizarro::BOP::subtract', isCommand => 1 }, # override -
+ ' ' => { class => 'bizarro::BOP::multiply', isCommand => 1 }, # override *
+ '*' => { class => 'bizarro::BOP::multiply', isCommand => 1 }, # override *
+ '* ' => { class => 'bizarro::BOP::multiply', isCommand => 1 }, # override *
+ ' *' => { class => 'bizarro::BOP::multiply', isCommand => 1 }, # override *
+ '/' => { class => 'bizarro::BOP::divide', isCommand => 1 }, # override /
+ '/ ' => { class => 'bizarro::BOP::divide', isCommand => 1 }, # override /
+ ' /' => { class => 'bizarro::BOP::divide', isCommand => 1 }, # override /
+ '//' => { class => 'bizarro::BOP::divide', isCommand => 1 }, # override /
+ );
+ $context->operators->undefine('^', '**');
+ $context->parens->undefine('|');
+ $context->operators->redefine('or', using => ',', from => 'Numeric');
+ $context->operators->redefine(',or', using => ',', from => 'Numeric');
+ $context->operators->redefine(', or', using => ',', from => 'Numeric');
+ $context->operators->redefine('and', using => ',', from => 'Numeric');
+ $context->operators->redefine(',and', using => ',', from => 'Numeric');
+ $context->operators->redefine(', and', using => ',', from => 'Numeric');
+ $context->strings->add(
+ "no real solutions" => {},
+ "no real solution" => { alias => 'no real solutions' },
+ "none" => { alias => 'no real solutions' },
+ "no solution" => { alias => 'no real solutions' },
+ "no solutions" => { alias => 'no real solutions' },
+ #Hack. Investigate making all of this be a constant.
+ "{}" => { alias => 'no real solutions' },
+ "{ }" => { alias => 'no real solutions' },
+ "{ }" => { alias => 'no real solutions' },
+ "{ }" => { alias => 'no real solutions' },
+ "infinitely many solutions" => {},
+ "infinitely many" => { alias => 'infinitely many solutions' },
+ "infinite solutions" => { alias => 'infinitely many solutions' },
+ "infinite" => { alias => 'infinitely many solutions' },
+ );
+ $context->functions->set(sqrt => { class => 'finiteSolutionSets::Function::numeric' }); # override sqrt()
+ $context->functions->add(identity => { class => 'finiteSolutionSets::Function::numeric' });
+ parser::Root->Enable($context);
+ $context->functions->set(root => { class => 'finiteSolutionSets::Function::numeric2' }); # override root()
+ $context->lists->set(List => { open => "{", close => "}" });
+ $context->{cmpDefaults}{Formula}{checker} = sub {
+ my ($correct, $student, $ans) = @_;
+ return 0 if $ans->{isPreview} || $correct != $student;
+ if ($student->class eq 'String') { return 1 if $correct == $student }
+ $student = $ans->{student_formula};
+ $student = Formula("identity($student)"); #ensure student is parsed as Formula object
+ my $setSqrt = Context()->flag("setSqrt");
+ my $setRoot = Context()->flag("setRoot");
+ my $useBizarro = (Context()->flag("useBizarro")) ? 1 : 0;
+ Context()->flags->set(
+ checkSqrt => $setSqrt,
+ checkRoot => $setRoot,
+ bizarroAdd => $useBizarro,
+ bizarroSub => $useBizarro,
+ bizarroMul => $useBizarro,
+ bizarroDiv => $useBizarro
+ );
+ delete $correct->{test_points};
+ delete $student->{test_points};
+ delete $correct->{test_values};
+ delete $student->{test_values};
+ my $OK = ($correct == $student);
+ Context()->flags->set(
+ checkSqrt => 0,
+ checkRoot => 0,
+ bizarroAdd => 0,
+ bizarroSub => 0,
+ bizarroMul => 0,
+ bizarroDiv => 0
+ );
+ Value::Error(Context()->flag('wrongFormMessage')) unless $OK;
+ return $OK;
+ };
+ $context->{cmpDefaults}{Formula}{requireParenMatch} = 0;
+ $context->{cmpDefaults}{Formula}{list_checker} = sub {
+ my ($correct, $student, $ansHash, $value) = @_;
+ my $score = 0; # number of correct student answers
+ my @errors = (); # stores error messages
+ my ($i, $j, $k); # loop counters
+ my $studentFormula = $ansHash->{student_formula};
+
+ # Student answer needs to be a set, list, a single number, or a single assignment
+ my $studentTypeOK = 0;
+ if ($studentFormula->type eq 'Set') { $studentTypeOK = 1 }
+ if ($studentFormula->type eq 'List') { $studentTypeOK = 1 }
+ if ($studentFormula->type eq 'Assignment') { $studentTypeOK = 1 }
+ if ($studentFormula->class eq 'Formula' and $studentFormula->type eq 'Number') {
+ if ($studentFormula->isConstant) { $studentTypeOK = 1 }
+ }
+ if ($studentFormula->type eq 'String') { return (0) }
+
+ push(@errors, 'Your answer is not a list of numbers or assignments') unless $studentTypeOK;
+ return (0, @errors) unless $studentTypeOK;
+
+ # Array of the student answers
+ my @studentanswers;
+ if ($studentFormula->type eq 'Assignment') { @studentanswers = ($studentFormula) }
+ if ($studentFormula->class eq 'Formula' and $studentFormula->type eq 'Number') {
+ @studentanswers = ($studentFormula);
+ }
+ if ($studentFormula->type eq 'List') { @studentanswers = $studentFormula->value }
+ if ($studentFormula->type eq 'Set') { @studentanswers = $studentFormula->value }
+ my $n = scalar(@studentanswers); # number of student answers
+
+ # Array of the correct answers
+ my @correctanswers = $ansHash->{correct_value}->value;
+ my $m = scalar(@correctanswers); # number of correct answers
+
+ #
+ # Loop though the student answers
+ ##
+ for ($i = 0; $i < $n; $i++) {
+ my $ith = ($n == 1) ? '' : Value::List->NameForNumber($i + 1);
+ my $p = $studentanswers[$i]; # i-th student answer
+ #
+ # Check that the student's answer is a number or assignment
+ #
+ if ($p->class ne "Formula" or $p->type ne "Number" and $p->type ne "Assignment") {
+ push(@errors, "Your $ith answer is not a number or assignment");
+ $score--;
+ next;
+ }
+ if ($p->class eq "Formula" and $p->type eq "Number") {
+ if (not($p->isConstant)) {
+ push(@errors, "Your $ith answer is not a number or assignment");
+ $score--;
+ next;
+ }
+ }
+ #
+ # For assignments, grab number
+ #
+ my $q = ($p->type eq "Assignment") ? Formula((split('=', $p))[1]) : $p;
+ #
+ # Check that the number isn't an unreduced fraction
+ #
+ $mycontext = Context();
+ Context("Fraction");
+ Context()->flags->set(reduceFractions => 0);
+ my $qfrac = Compute("$q");
+ if ($qfrac->class eq 'Fraction') {
+ do { push(@errors, $q->string . " is not a reduced fraction"); $score--; next; }
+ unless $qfrac->isReduced;
+ do { push(@errors, $q->string . " can be simplified"); $score--; next; }
+ unless (($qfrac->value)[1] != 1);
+ }
+ Context($mycontext);
+ #
+ # Check that the number hasn't been given before
+ #
+ for ($j = 0, $used = 0; $j < $i; $j++) {
+ my $r = $studentanswers[$j];
+ if ($r->class eq "Formula" and $r->type eq "Number" and $r == $q) {
+ push(@errors, "Your $ith answer is the same as a previous one") unless $ansHash->{isPreview};
+ $used = 1;
+ $score--;
+ last;
+ }
+ if ($r->type eq "Assignment") {
+ $r = Formula((split('=', $r))[1]);
+ if ($r == $q) {
+ push(@errors, "Your $ith answer is the same as a previous one")
+ unless $ansHash->{isPreview};
+ $used = 1;
+ $score--;
+ last;
+ }
+ }
+ }
+ #
+ # If not already used, compare to each of the correct answers
+ # and increase the score if there is a match. If there is no
+ # match, take note if the failure is because of form
+ #
+ if (!$used) {
+ my $qcmp;
+ for ($k = 0, $match = 0, $badform = 0; $k < $m; $k++) {
+ my $s = $correctanswers[$k];
+ $qcmp = $s->cmp->evaluate($q->string);
+ if ($qcmp->{score}) { $match = 1; last; }
+ if ($qcmp->{ans_message} eq Context()->flag('wrongFormMessage')) { $badform = 1; }
+ }
+ if ($match == 1) { $score++ }
+ else {
+ $score--;
+ do {
+ if ($badform == 1) {
+ push(@errors, Context()->flag('wrongFormMessage') =~ s/Your answer/Your $ith answer/gr);
+ } elsif ($n != 1) {
+ push(@errors, "Your $ith answer is not correct");
+ }
+ } unless $ansHash->{isPreview};
+ }
+ }
+ }
+ #
+ # Check that there are the right number of answers
+ #
+ if (!$ansHash->{isPreview}) {
+ push(@errors, "You need to provide more numbers") if $n < $m and $score == $n;
+ push(@errors, "You have given too many answers") if $score > $m;
+ }
+ #
+ # Express a preference for formatting
+ #
+ if ($studentFormula->type ne 'Set' and $m == $score and Context()->flags->get('preferSetNotation') == 1) {
+ push(@errors,
+ "The preferred notation for the solution set is${BR}\\(\\left\\{"
+ . join(',', map { $_->TeX } (@correctanswers))
+ . "\\right\\}\\)");
+ }
+ return ($score, @errors);
+ };
+}
+
+###########################
+#
+# Subclass the numeric functions
+#
+package finiteSolutionSets::Function::numeric;
+our @ISA = ('Parser::Function::numeric');
+
+#
+# Override sqrt() to return a special value times x when evaluated
+#
+sub sqrt {
+ my $self = shift;
+ my $x = shift;
+ my $value = $self->context->flag("checkSqrt");
+ return $value + 1 if $value && $x == 1; # force sqrt(1) to be incorrect
+ return $value + 1 if $value && $x == $value; # force sqrt(sqrt(x)) to be incorrect
+ return $value + 1 if $value && $x == $self->context->flag("checkRoot"); # force sqrt(root(n,x))) to be incorrect
+ return $value * $x if $value;
+ return $self->SUPER::sqrt($x);
+}
+
+sub identity {
+ my $self = shift;
+ my $x = shift;
+ return $x;
+}
+
+package finiteSolutionSets::Function::numeric2;
+our @ISA = ('parser::Root::Function::numeric2');
+#
+# Override root(n,) to return a special value times x when evaluated
+#
+sub root {
+ my $self = shift;
+ my ($n, $x) = @_;
+ my $value = $self->context->flag("checkRoot");
+ return $value if $value && $x == 1; # force root(n,1) to be incorrect
+ return $value if $value && $n == 1; # force root(1,x) to be incorrect
+ return $value if $value && $x == $value; # force root(n,root(n,x)) to be incorrect
+ return $value if $value && $x == $self->context->flag("checkSqrt"); # force root(n,sqrt(x)) to be incorrect
+ return $value * $x if $value;
+ return $self->SUPER::root($n, $x);
+}
+
diff --git a/macros/contexts/contextForm.pl b/macros/contexts/contextForm.pl
new file mode 100644
index 0000000000..83a7fa5ef4
--- /dev/null
+++ b/macros/contexts/contextForm.pl
@@ -0,0 +1,150 @@
+################################################################################
+# WeBWorK Online Homework Delivery System
+# Copyright © 2000-2018 The WeBWorK Project, http://openwebwork.sf.net/
+# $CVSHeader$
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of either: (a) the GNU General Public License as published by the
+# Free Software Foundation; either version 2, or (at your option) any later
+# version, or (b) the "Artistic License" which comes with this package.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the
+# Artistic License for more details.
+################################################################################
+
+=head1 NAME
+
+This context distinguishes between "forms" of an expression by using bizarro
+arithmetic.
+
+For example, the answer could be "(x+1)(x+2)". Bizarro arithmetic always has
+commutative and associative addidition and multiplication, so it would be OK
+to anwer with "(2+x)(x+1)".
+
+But this context initally only uses bizarro with multiplication and division.
+So "x^2+3x+2" will not evaluate to the same as "(x+1)(x+2)".
+
+The reverse works as well: the answer could be "x^2+3x+2" and "(x+1)(x+2)"
+will not be accepted.
+
+In general if you have a problem where "form" matters, try this context. It
+may not always work for you out of the box. But even then you may be able to
+adjust the bizarro details to make it work.
+
+For example if you wanted to factor x^2+2x+1 and you declare "(x+1)^2" to be
+the answer, at first it will not accept "(x+1)(x+1)". Because bizarro exponents
+are not activated. But you could activate them (or deactivate bizarro multiplication
+and division while activating bizarro addition and subtraction) and then
+"(x+1)^2" and "(x+1)(x+1)" would be equivalent, yet distinct from "x^2+2x+1".
+
+
+=head1 DESCRIPTION
+
+=cut
+
+loadMacros("MathObjects.pl", "bizarroArithmetic.pl", "parserRoot.pl",);
+
+sub _contextForm_init {
+ my $context = $main::context{Form} = Parser::Context->getCopy("Numeric");
+ Parser::Number::NoDecimals($context);
+ $context->{name} = "Form";
+ $context->flags->set(
+ reduceConstants => 0,
+ reduceConstantFunctions => 0,
+ formatStudentAnswer => "parsed",
+ checkSqrt => 0,
+ checkRoot => 0,
+ setSqrt => exp(1) / main::ln(2),
+ setRoot => exp(2) / main::ln(3),
+ wrongFormMessage =>
+ 'Your answer is algebraically equivalent to the correct answer, but not in the expected form. Maybe it is not fully simplified. Maybe something is not completely factored or expanded. Maybe it is not in the expected form for some other reason.',
+
+ );
+ $context->noreduce('(-x)+y', '(-x)-y');
+ $context->operators->set(
+ '*' => { class => 'bizarro::BOP::multiply', isCommand => 1 }, # override *
+ '* ' => { class => 'bizarro::BOP::multiply', isCommand => 1 }, # override *
+ ' *' => { class => 'bizarro::BOP::multiply', isCommand => 1 }, # override *
+ '/' => { class => 'bizarro::BOP::divide', isCommand => 1 }, # override /
+ '/ ' => { class => 'bizarro::BOP::divide', isCommand => 1 }, # override /
+ ' /' => { class => 'bizarro::BOP::divide', isCommand => 1 }, # override /
+ );
+ $context->functions->set(sqrt => { class => 'form::Function::numeric' }); # override sqrt()
+ parser::Root->Enable($context);
+ $context->functions->set(root => { class => 'form::Function::numeric2' }); # override root()
+ $context->{cmpDefaults}{Formula}{checker} = sub {
+ my ($correct, $student, $ans) = @_;
+ return 0 if $ans->{isPreview} || $correct != $student;
+ $student = $ans->{student_formula};
+ my $setSqrt = Context()->flag("setSqrt");
+ my $setRoot = Context()->flag("setRoot");
+ Context()->flags->set(
+ checkSqrt => $setSqrt,
+ checkRoot => $setRoot,
+ bizarroAdd => 1,
+ bizarroSub => 1,
+ bizarroMul => 1,
+ bizarroDiv => 1
+ );
+ delete $correct->{test_values};
+ delete $student->{test_values};
+ my $OK = ($correct == $student);
+ Context()->flags->set(
+ checkSqrt => 0,
+ checkRoot => 0,
+ bizarroAdd => 0,
+ bizarroSub => 0,
+ bizarroMul => 0,
+ bizarroDiv => 0
+ );
+ Value::Error(Context()->flag('wrongFormMessage')) unless $OK;
+ return $OK;
+ };
+ $context->strings->add(
+ 'not a real number' => {},
+ 'not real' => { alias => 'not a real number' },
+ 'does not exist' => { alias => 'not a real number' },
+ "doesn't exist" => { alias => 'not a real number' },
+ );
+}
+
+###########################
+#
+# Subclass the numeric functions
+#
+package form::Function::numeric;
+our @ISA = ('Parser::Function::numeric');
+
+#
+# Override sqrt() to return a special value times x when evaluated
+#
+sub sqrt {
+ my $self = shift;
+ my $x = shift;
+ my $value = $self->context->flag("checkSqrt");
+ return $value + 1 if $value && $x == 1; # force sqrt(1) to be incorrect
+ return $value + 1 if $value && $x == $value; # force sqrt(sqrt(x)) to be incorrect
+ return $value + 1 if $value && $x == $self->context->flag("checkRoot"); # force sqrt(root(n,x))) to be incorrect
+ return $value * $x if $value;
+ return $self->SUPER::sqrt($x);
+}
+
+package form::Function::numeric2;
+our @ISA = ('parser::Root::Function::numeric2');
+#
+# Override root(n,) to return a special value times x when evaluated
+#
+sub root {
+ my $self = shift;
+ my ($n, $x) = @_;
+ my $value = $self->context->flag("checkRoot");
+ return $value if $value && $x == 1; # force root(n,1) to be incorrect
+ return $value if $value && $n == 1; # force root(1,x) to be incorrect
+ return $value if $value && $x == $value; # force root(n,root(n,x)) to be incorrect
+ return $value if $value && $x == $self->context->flag("checkSqrt"); # force root(n,sqrt(x)) to be incorrect
+ return $value * $x if $value;
+ return $self->SUPER::root($n, $x);
+}
+
diff --git a/macros/contextFraction.pl b/macros/contexts/contextFraction.pl
similarity index 100%
rename from macros/contextFraction.pl
rename to macros/contexts/contextFraction.pl
diff --git a/macros/contexts/contextFunctionAssign.pl b/macros/contexts/contextFunctionAssign.pl
new file mode 100644
index 0000000000..2ec50a1a42
--- /dev/null
+++ b/macros/contexts/contextFunctionAssign.pl
@@ -0,0 +1,21 @@
+######################################################################
+#Description: macro to load parserAssignment and change the error
+# message to be more specific for a function. Requires
+# answers be submitted in the form:
+# y=formula or f(x)=formula
+######################################################################
+loadMacros("parserAssignment.pl");
+
+sub parser::Assignment::Formula::cmp_equal {
+ my $self = shift;
+ my $ans = shift;
+ Value::cmp_equal($self, $ans);
+ if ($ans->{ans_message} =~ m/Your answer isn't.*it looks like/s) {
+ $ans->{ans_message} =
+ "Warning: Your answer should be of the form: '" . $self->{tree}{lop}->string . "= formula'";
+ }
+}
+
+######################################################################
+
+1;
diff --git a/macros/contextInequalities.pl b/macros/contexts/contextInequalities.pl
similarity index 100%
rename from macros/contextInequalities.pl
rename to macros/contexts/contextInequalities.pl
diff --git a/macros/contexts/contextInequalitiesAllowStrings.pl b/macros/contexts/contextInequalitiesAllowStrings.pl
new file mode 100644
index 0000000000..1291dc666e
--- /dev/null
+++ b/macros/contexts/contextInequalitiesAllowStrings.pl
@@ -0,0 +1,54 @@
+###
+
+=head1 NAME
+
+ extra macros for Intermediate Algebra problems at CofI
+
+=head1 Synposis
+ macros by R Cruz -- The College of Idaho
+=cut
+
+=head3 Allows string answers for the set of real numbers and the empty set
+
+=pod
+ Adds the string "All real numbers" to the Inequalities context
+ NOT WORKING: Adds the string "No solution" to the Inequalities context
+
+=cut
+
+loadMacros("contextInequalities.pl");
+
+sub _contextInequalitiesAllowStrings_init {
+ my $context = $main::context{"Inequalities-AllowStrings"} = Parser::Context->getCopy("Inequalities");
+
+ $context->constants->{namePattern} = qr/.*/;
+
+ #----Add variations of "All real numbers"
+
+ $context->constants->redefine("All real numbers", from => "Interval", using => "R");
+ $context->constants->set("All real numbers" => { TeX => "\\mbox{All real numbers}" });
+
+ $context->constants->redefine("all real numbers", from => "Interval", using => "R");
+ $context->constants->set("all real numbers" => { TeX => "\\mbox{all real numbers}" });
+
+ $context->constants->redefine("All Real numbers", from => "Interval", using => "R");
+ $context->constants->set("All Real numbers" => { TeX => "\\mbox{All Real numbers}" });
+
+ $context->constants->redefine("ALL REAL NUMBERS", from => "Interval", using => "R");
+ $context->constants->set("ALL REAL NUMBERS" => { TeX => "\\mbox{ALL REAL NUMBERS}" });
+
+ #-----Add variations of "No solution" = NONE
+ #************This part does not work. Can't get it to "take" the empty set
+ # Tried Set(), NONE and DNE
+ #
+ # $context->constants->redefine("No solution"",from=>"Interval",using=>"{}");
+ # $context->constants->set("No solution"=>{TeX=>"\\mbox{No solution}"});
+ #
+ # $context->constants->redefine("no solution"",from=>"Interval",using=>"{}");
+ # $context->constants->set("no solution"=>{TeX=>"\\mbox{no solution}"});
+ #
+ # $context->constants->redefine("NO SOLUTION"",from=>"Interval",using=>"{}");
+ # $context->constants->set("NO SOLUTION"=>{TeX=>"\\mbox{NO SOLUTION}"});
+
+}
+1;
diff --git a/macros/contextInequalitySetBuilder.pl b/macros/contexts/contextInequalitySetBuilder.pl
similarity index 100%
rename from macros/contextInequalitySetBuilder.pl
rename to macros/contexts/contextInequalitySetBuilder.pl
diff --git a/macros/contextInteger.pl b/macros/contexts/contextInteger.pl
similarity index 100%
rename from macros/contextInteger.pl
rename to macros/contexts/contextInteger.pl
diff --git a/macros/contextIntegerFunctions.pl b/macros/contexts/contextIntegerFunctions.pl
similarity index 100%
rename from macros/contextIntegerFunctions.pl
rename to macros/contexts/contextIntegerFunctions.pl
diff --git a/macros/contextLeadingZero.pl b/macros/contexts/contextLeadingZero.pl
similarity index 100%
rename from macros/contextLeadingZero.pl
rename to macros/contexts/contextLeadingZero.pl
diff --git a/macros/contextLimitedComplex.pl b/macros/contexts/contextLimitedComplex.pl
similarity index 100%
rename from macros/contextLimitedComplex.pl
rename to macros/contexts/contextLimitedComplex.pl
diff --git a/macros/contextLimitedFactor.pl b/macros/contexts/contextLimitedFactor.pl
similarity index 100%
rename from macros/contextLimitedFactor.pl
rename to macros/contexts/contextLimitedFactor.pl
diff --git a/macros/contextLimitedNumeric.pl b/macros/contexts/contextLimitedNumeric.pl
similarity index 100%
rename from macros/contextLimitedNumeric.pl
rename to macros/contexts/contextLimitedNumeric.pl
diff --git a/macros/contextLimitedPoint.pl b/macros/contexts/contextLimitedPoint.pl
similarity index 100%
rename from macros/contextLimitedPoint.pl
rename to macros/contexts/contextLimitedPoint.pl
diff --git a/macros/contextLimitedPolynomial.pl b/macros/contexts/contextLimitedPolynomial.pl
similarity index 100%
rename from macros/contextLimitedPolynomial.pl
rename to macros/contexts/contextLimitedPolynomial.pl
diff --git a/macros/contextLimitedPowers.pl b/macros/contexts/contextLimitedPowers.pl
similarity index 100%
rename from macros/contextLimitedPowers.pl
rename to macros/contexts/contextLimitedPowers.pl
diff --git a/macros/contextLimitedRadical.pl b/macros/contexts/contextLimitedRadical.pl
similarity index 100%
rename from macros/contextLimitedRadical.pl
rename to macros/contexts/contextLimitedRadical.pl
diff --git a/macros/contexts/contextLimitedRadicalComplex.pl b/macros/contexts/contextLimitedRadicalComplex.pl
new file mode 100644
index 0000000000..e648f52e1c
--- /dev/null
+++ b/macros/contexts/contextLimitedRadicalComplex.pl
@@ -0,0 +1,180 @@
+
+=head1 NAME
+contextLimitedRadical.pl - defines a root(n,x) function for n-th root of x, and
+allows for specification of forms of radical answers, like simplified radicals or
+with rational denominators
+
+=head1 DESCRIPTION
+
+This file enables a context LimitedRadical in which students and pg-authors may use
+root(n,x) for the nth root of x. The function makes sure n!=0, and also allows for
+negative x only if n is an odd integer.
+
+By default, the limits for Formula answer checking are [0,5] to avoid negatives in
+radicals. If it is important to distinguish sqrt(x^2) as |x| rather than x, then the
+limits should be changed to include a region where they differ.
+
+The context requires that radical expressions (using either sqrt() or root()) meet
+the form of the author's answer with respect to simplification and rationalization
+of denominators.
+
+To accomplish this, Math Objects like "root(3,2)" should be created as Formula("root(3,2)"),
+not Compute("root(3,2)"), or they will be treated as Reals and evaluated as decimals.
+Compute("root(3,x)") should be fine though, since it is unambiguously a Formula.
+
+Student answers are first compared to the correct answer in the usual way to see if
+they are at least numerically correct. Then to check that expressions are fully
+simplified, during a check, both sqrt(x) and root(n,x) are temporarily replaced by certain
+other functions. Also +, -, *, and / are temporarily replaced with bizarro arithmetic
+(from bizarroArithmetic.pl) that nearly make for a bizarro field structure on R.
+If the student and author answers disagree after this change, then the student's answer
+is not in form of the author's answer.
+
+For example "1+2" is not equivalent to "3" under bizarro addition. "2sqrt(2)" is not
+equivalent to "sqrt(8)" with the bizarro arithmetic. Any radical applied to 1 is declared
+unsimplified as well.
+
+If a student's answer is numerically equal to the author's, but not equal under bizarro
+arithmetic, students get the message that their answer is not fully simplified. So care
+should be taken by the problem author when defining answers.
+
+The near-field structure of the bizarro versions means that "1+sqrt(2)+sqrt(3)" can be
+entered as "sqrt(3)+sqrt(2)+1", etc, and not be declared "unsimplified". Also "(1+sqrt(2))/2"
+can be entered as "1/2+sqrt(2)/2" without being declared "unsimplified". However,
+"(1+2sqrt(2))/2" *will* be declared different from "1/2+sqrt(2)", even though both are
+generally considered fully simplified. If an author can foresee this arising, consider
+using parserOneOf.pl to allow for multiple forms of the correct answer.
+
+Technically both "sqrt(3)/3" and "1/sqrt(3)" are fully simplified, although you may want
+rational denominators. This context will declare them equal but not equivalent. The author
+should either accept both as correct using parserOneOf.pl, or give the message that the
+student's answer does not have a rational denominator using answerHints.pl.
+
+Note that there is nothing here that is actually doing any reducing of radical quantities
+or honestly checking for "simplified" answers - so authors need to make sure their answers
+are reduced and simplified. Also if the correct answer is "sqrt(2)" and the student
+enters "sqrt(3)+sqrt(3)" or "4sqrt(2)/2", they will just be told they are wrong, not that
+their (incorrect) answer is unsimplified.
+
+
+=cut
+
+loadMacros(
+ "contextLimitedPowers.pl",
+ "bizarroArithmetic.pl",
+
+);
+
+#
+# Set up the LimitedRadical context
+#
+sub _contextLimitedRadicalComplex_init {
+ my $context = $main::context{LimitedRadicalComplex} = Parser::Context->getCopy("Complex");
+ Parser::Number::NoDecimals($context);
+ $context->flags->set(setSqrt => exp(1) / ln(2), setRoot => exp(1) / ln(2));
+ $context->operators->set(
+ '+' => { class => 'bizarro::BOP::add', isCommand => 1 }, # override +
+ '-' => { class => 'bizarro::BOP::subtract', isCommand => 1 }, # override -
+ '*' => { class => 'bizarro::BOP::multiply', isCommand => 1 }, # override *
+ '* ' => { class => 'bizarro::BOP::multiply', isCommand => 1 }, # override *
+ ' *' => { class => 'bizarro::BOP::multiply', isCommand => 1 }, # override *
+ '/' => { class => 'bizarro::BOP::divide', isCommand => 1 }, # override /
+ '/ ' => { class => 'bizarro::BOP::divide', isCommand => 1 }, # override /
+ ' /' => { class => 'bizarro::BOP::divide', isCommand => 1 }, # override /
+ '//' => { class => 'bizarro::BOP::divide', isCommand => 1 }, # override /
+ );
+ $context->functions->set(sqrt => { class => 'my::Function::numeric' }); # override sqrt()
+ main::PG_restricted_eval('sub root {Parser::Function->call("root",@_)}');
+ $context->functions->add(root => { class => 'my::Function::numeric2' },);
+ $context->flags->set(limits => [ 0, 5 ]); # no negatives in the radicals
+ $context->flags->set(
+ reduceConstantFunctions => 0,
+ reduceConstants => 0,
+ formatStudentAnswer => 'parsed',
+ checkSqrt => 0,
+ checkRoot => 0
+ );
+ LimitedPowers::OnlyPositiveIntegers($context); # don't allow powers of 1/2, 1/3, etc
+ $context->{cmpDefaults}{Formula}{checker} = sub {
+ my ($correct, $student, $ans) = @_;
+ return 0 if $ans->{isPreview} || $correct != $student;
+ $student = $ans->{student_formula};
+ $correct = $correct->{original_formula} if defined $correct->{original_formula};
+ $student = Formula("$student");
+ $correct = Formula("$correct"); #ensure both are Formula objects
+ my ($setSqrt, $setRoot) = (Context()->flag("setSqrt"), Context()->flag("setRoot"));
+ Context()->flags->set(
+ checkSqrt => $setSqrt,
+ checkRoot => $setRoot,
+ bizarroAdd => 1,
+ bizarroSub => 1,
+ bizarroMul => 1,
+ bizarroDiv => 1
+ );
+ delete $correct->{test_values};
+ delete $student->{test_values};
+ my $OK = (($correct == $student) or ($student == $correct)); # check if equal when sqrt's are replaced by 1
+ Context()->flags->set(
+ checkSqrt => 0,
+ checkRoot => 0,
+ bizarroAdd => 0,
+ bizarroSub => 0,
+ bizarroMul => 0,
+ bizarroDiv => 0
+ );
+ Value::Error("You must simplify your answer further") unless $OK;
+ return $OK;
+ };
+}
+
+###########################
+#
+# Create root(n, x)
+#
+
+package my::Function::numeric2;
+our @ISA = ('Parser::Function::numeric2');
+
+sub root {
+ my $self = shift;
+ my $n = shift;
+ my $x = shift;
+ $self->Error("Can't take 0th roots") if ($n == 0);
+ $self->Error("Can't take general roots of negative numbers") if (($x < 0) and (($n - 1) / 2 != int(($n - 1) / 2)));
+ my $value = $self->context->flag("checkRoot");
+ return $value + 1 if $value && $x == 1; # force root(n,1) to be incorrect
+ return $value + 1 if $value && $x == $value; # force root(n,root(m,x))) to be incorrect
+ return $value + 1 if $value && $x == $self->context->flag("checkSqrt"); # force root(n,sqrt(x))) to be incorrect
+ return $value * $x if $value;
+ return ($x)**(1 / $n) if (($x > 0) and ($n != 0));
+ return -(abs($x)**(1 / $n)) if (($x < 0) and (($n - 1) / 2 == int(($n - 1) / 2)));
+}
+
+sub TeX {
+ my $self = shift;
+ my ($n, $x) = ($self->{params}[0], $self->{params}[1]);
+ return '\sqrt[' . $n->TeX . "]{" . $x->TeX . "}";
+}
+
+###########################
+#
+# Subclass the numeric functions
+#
+package my::Function::numeric;
+our @ISA = ('Parser::Function::numeric');
+
+#
+# Override sqrt() to return a special value times x when evaluated
+#
+sub sqrt {
+ my $self = shift;
+ my $x = shift;
+ my $value = $self->context->flag("checkSqrt");
+ return $value + 1 if $value && $x == 1; # force sqrt(1) to be incorrect
+ return $value + 1 if $value && $x == $value; # force sqrt(sqrt(x)) to be incorrect
+ return $value + 1 if $value && $x == $self->context->flag("checkRoot"); # force sqrt(root(n,x))) to be incorrect
+ return $value * $x if $value;
+ return $self->SUPER::sqrt($x);
+}
+
+1;
diff --git a/macros/contextLimitedVector.pl b/macros/contexts/contextLimitedVector.pl
similarity index 100%
rename from macros/contextLimitedVector.pl
rename to macros/contexts/contextLimitedVector.pl
diff --git a/macros/contextMatrixExtras.pl b/macros/contexts/contextMatrixExtras.pl
similarity index 100%
rename from macros/contextMatrixExtras.pl
rename to macros/contexts/contextMatrixExtras.pl
diff --git a/macros/contextOrdering.pl b/macros/contexts/contextOrdering.pl
similarity index 100%
rename from macros/contextOrdering.pl
rename to macros/contexts/contextOrdering.pl
diff --git a/macros/contextPartition.pl b/macros/contexts/contextPartition.pl
similarity index 100%
rename from macros/contextPartition.pl
rename to macros/contexts/contextPartition.pl
diff --git a/macros/contextPercent.pl b/macros/contexts/contextPercent.pl
similarity index 100%
rename from macros/contextPercent.pl
rename to macros/contexts/contextPercent.pl
diff --git a/macros/contextPeriodic.pl b/macros/contexts/contextPeriodic.pl
similarity index 100%
rename from macros/contextPeriodic.pl
rename to macros/contexts/contextPeriodic.pl
diff --git a/macros/contextPermutation.pl b/macros/contexts/contextPermutation.pl
similarity index 100%
rename from macros/contextPermutation.pl
rename to macros/contexts/contextPermutation.pl
diff --git a/macros/contextPermutationUBC.pl b/macros/contexts/contextPermutationUBC.pl
similarity index 100%
rename from macros/contextPermutationUBC.pl
rename to macros/contexts/contextPermutationUBC.pl
diff --git a/macros/contextPiecewiseFunction.pl b/macros/contexts/contextPiecewiseFunction.pl
similarity index 100%
rename from macros/contextPiecewiseFunction.pl
rename to macros/contexts/contextPiecewiseFunction.pl
diff --git a/macros/contextPolynomialFactors.pl b/macros/contexts/contextPolynomialFactors.pl
similarity index 100%
rename from macros/contextPolynomialFactors.pl
rename to macros/contexts/contextPolynomialFactors.pl
diff --git a/macros/contexts/contextRationalExponent.pl b/macros/contexts/contextRationalExponent.pl
new file mode 100644
index 0000000000..78ff4aeb38
--- /dev/null
+++ b/macros/contexts/contextRationalExponent.pl
@@ -0,0 +1,146 @@
+
+=head1 NAME
+
+contextRationalExponent.pl - allows the requirement of simplified rational exponent answers. May be a misnomer since nothing requires exponents to be rational.
+
+=head1 DESCRIPTION
+
+This code is a copy of contextLimitedRadical.pl, with the following changes:
+- no need for contextLimitedPowers.pl, since we need to use bizarro power, so that macro is removed
+- name of the context changed to RationalExponent
+- set class of ^ and ** to bizarro
+- removed LimitedPowers::OnlyPositiveIntegers($context) call
+- set bizarroPow flags in answer checking (on, then off)
+- undefined sqrt and root (after root is added to the context). These can be redefined by a problem author if desired.
+- added special messages if sqrt or root are used when they were undefined
+
+=cut
+
+loadMacros(
+ "bizarroArithmetic.pl",
+
+);
+
+#
+# Set up the RationalExponent context
+#
+sub _contextRationalExponent_init {
+ my $context = $main::context{RationalExponent} = Parser::Context->getCopy("Numeric");
+ Parser::Number::NoDecimals($context);
+ $context->flags->set(setSqrt => exp(1) / ln(2), setRoot => exp(1) / ln(2));
+ $context->operators->set(
+ '+' => { class => 'bizarro::BOP::add', isCommand => 1 }, # override +
+ '-' => { class => 'bizarro::BOP::subtract', isCommand => 1 }, # override -
+ '*' => { class => 'bizarro::BOP::multiply', isCommand => 1 }, # override *
+ '* ' => { class => 'bizarro::BOP::multiply', isCommand => 1 }, # override *
+ ' *' => { class => 'bizarro::BOP::multiply', isCommand => 1 }, # override *
+ '/' => { class => 'bizarro::BOP::divide', isCommand => 1 }, # override /
+ '/ ' => { class => 'bizarro::BOP::divide', isCommand => 1 }, # override /
+ ' /' => { class => 'bizarro::BOP::divide', isCommand => 1 }, # override /
+ '//' => { class => 'bizarro::BOP::divide', isCommand => 1 }, # override /
+ '**' => { class => 'bizarro::BOP::power', isCommand => 1, perl => undef }, # override **
+ '^' => { class => 'bizarro::BOP::power', isCommand => 1, perl => undef }, # override ^
+ );
+ $context->functions->set(sqrt => { class => 'my::Function::numeric' }); # override sqrt()
+ main::PG_restricted_eval('sub root {Parser::Function->call("root",@_)}');
+ $context->functions->add(root => { class => 'my::Function::numeric2' },);
+ $context->flags->set(limits => [ 0, 5 ]); # no negatives in the radicals
+ $context->flags->set(
+ reduceConstantFunctions => 0,
+ reduceConstants => 0,
+ formatStudentAnswer => 'parsed',
+ checkSqrt => 0,
+ checkRoot => 0
+ );
+ $context->functions->undefine('sqrt');
+ $context->functions->undefine('root');
+ $context->{error}{msg}{"Function 'root' is not allowed in this context"} =
+ "Please use rational exponents, not root or sqrt";
+ $context->{error}{msg}{"Function 'sqrt' is not allowed in this context"} =
+ "Please use rational exponents, not sqrt or root";
+ $context->{cmpDefaults}{Formula}{checker} = sub {
+ my ($correct, $student, $ans) = @_;
+ return 0 if $ans->{isPreview} || $correct != $student;
+ $student = $ans->{student_formula};
+ $correct = $correct->{original_formula} if defined $correct->{original_formula};
+ $student = Formula("$student");
+ $correct = Formula("$correct"); #ensure both are Formula objects
+ my ($setSqrt, $setRoot) = (Context()->flag("setSqrt"), Context()->flag("setRoot"));
+ Context()->flags->set(
+ checkSqrt => $setSqrt,
+ checkRoot => $setRoot,
+ bizarroAdd => 1,
+ bizarroSub => 1,
+ bizarroMul => 1,
+ bizarroDiv => 1,
+ bizarroPow => 1
+ );
+ delete $correct->{test_values};
+ delete $student->{test_values};
+ my $OK = (($correct == $student) or ($student == $correct)); # check if equal when sqrt's are replaced by 1
+ Context()->flags->set(
+ checkSqrt => 0,
+ checkRoot => 0,
+ bizarroAdd => 0,
+ bizarroSub => 0,
+ bizarroMul => 0,
+ bizarroDiv => 0,
+ bizarroPow => 0
+ );
+ Value::Error("You must simplify your answer further") unless $OK;
+ return $OK;
+ };
+
+}
+
+###########################
+#
+# Create root(n, x)
+#
+
+package my::Function::numeric2;
+our @ISA = ('Parser::Function::numeric2');
+
+sub root {
+ my $self = shift;
+ my $n = shift;
+ my $x = shift;
+ $self->Error("Can't take 0th roots") if ($n == 0);
+ $self->Error("Can't take general roots of negative numbers") if (($x < 0) and (($n - 1) / 2 != int(($n - 1) / 2)));
+ my $value = $self->context->flag("checkRoot");
+ return $value + 1 if $value && $x == 1; # force root(n,1) to be incorrect
+ return $value + 1 if $value && $x == $value; # force root(n,root(m,x))) to be incorrect
+ return $value + 1 if $value && $x == $self->context->flag("checkSqrt"); # force root(n,sqrt(x))) to be incorrect
+ return $value * $x if $value;
+ return ($x)**(1 / $n) if (($x > 0) and ($n != 0));
+ return -(abs($x)**(1 / $n)) if (($x < 0) and (($n - 1) / 2 == int(($n - 1) / 2)));
+}
+
+sub TeX {
+ my $self = shift;
+ my ($n, $x) = ($self->{params}[0], $self->{params}[1]);
+ return '\sqrt[' . $n->TeX . "]{" . $x->TeX . "}";
+}
+
+###########################
+#
+# Subclass the numeric functions
+#
+package my::Function::numeric;
+our @ISA = ('Parser::Function::numeric');
+
+#
+# Override sqrt() to return a special value times x when evaluated
+#
+sub sqrt {
+ my $self = shift;
+ my $x = shift;
+ my $value = $self->context->flag("checkSqrt");
+ return $value + 1 if $value && $x == 1; # force sqrt(1) to be incorrect
+ return $value + 1 if $value && $x == $value; # force sqrt(sqrt(x)) to be incorrect
+ return $value + 1 if $value && $x == $self->context->flag("checkRoot"); # force sqrt(root(n,x))) to be incorrect
+ return $value * $x if $value;
+ return $self->SUPER::sqrt($x);
+}
+
+1;
diff --git a/macros/contextRationalFunction.pl b/macros/contexts/contextRationalFunction.pl
similarity index 100%
rename from macros/contextRationalFunction.pl
rename to macros/contexts/contextRationalFunction.pl
diff --git a/macros/contextReaction.pl b/macros/contexts/contextReaction.pl
similarity index 100%
rename from macros/contextReaction.pl
rename to macros/contexts/contextReaction.pl
diff --git a/macros/contexts/contextRestrictedDomains.pl b/macros/contexts/contextRestrictedDomains.pl
new file mode 100644
index 0000000000..a6d8286faf
--- /dev/null
+++ b/macros/contexts/contextRestrictedDomains.pl
@@ -0,0 +1,223 @@
+################################################################################
+# WeBWorK Online Homework Delivery System
+# Copyright @copy; 2000-2018 The WeBWorK Project, http://openwebwork.sf.net/
+# $CVSHeader$
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of either: (a) the GNU General Public License as published by the
+# Free Software Foundation; either version 2, or (at your option) any later
+# version, or (b) the "Artistic License" which comes with this package.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the
+# Artistic License for more details.
+################################################################################
+
+=head1 NAME
+
+Describe this context here.
+
+=head1 DESCRIPTION
+
+=cut
+
+loadMacros("MathObjects.pl", "bizarroArithmetic.pl", "contextInequalitySetBuilder.pl", "PGinfo.pl",);
+
+sub _contextRestrictedDomains_init {
+ my $context = $main::context{RestrictedDomains} = Parser::Context->getCopy("InequalitySetBuilder");
+ $context->{name} = "RestrictedDomains";
+ $context->flags->set(
+ reduceConstants => 0,
+ reduceConstantFunctions => 0,
+ formatStudentAnswer => "parsed",
+ checkSqrt => 0,
+ checkRoot => 0,
+ setSqrt => exp(1) / main::ln(2),
+ wrongFormMessage =>
+ 'Your answer is algebraically equivalent to the correct answer, but not in the expected form. Maybe it is not fully simplified. Maybe something is not completely factored. Maybe it is not in the expected form for some other reason.',
+ useBizarro => 1,
+ expressionWeight => 0.9,
+ );
+ $context->noreduce('(-x)+y', '(-x)-y');
+ $context->operators->set(
+ '+' => { class => 'bizarro::BOP::add', isCommand => 1 }, # override +
+ '-' => { class => 'bizarro::BOP::subtract', isCommand => 1 }, # override -
+ ' ' => { class => 'bizarro::BOP::multiply', isCommand => 1 }, # override *
+ '*' => { class => 'bizarro::BOP::multiply', isCommand => 1 }, # override *
+ '* ' => { class => 'bizarro::BOP::multiply', isCommand => 1 }, # override *
+ ' *' => { class => 'bizarro::BOP::multiply', isCommand => 1 }, # override *
+ '/' => { class => 'bizarro::BOP::divide', isCommand => 1 }, # override /
+ '/ ' => { class => 'bizarro::BOP::divide', isCommand => 1 }, # override /
+ ' /' => { class => 'bizarro::BOP::divide', isCommand => 1 }, # override /
+ '//' => { class => 'bizarro::BOP::divide', isCommand => 1 }, # override /
+ );
+ $context->operators->redefine('for', using => ',', from => 'Numeric');
+ $context->operators->redefine(',for', using => ',', from => 'Numeric');
+ $context->operators->redefine(', for', using => ',', from => 'Numeric');
+ $context->operators->redefine('where', using => ',', from => 'Numeric');
+ $context->operators->redefine(',where', using => ',', from => 'Numeric');
+ $context->operators->redefine(', where', using => ',', from => 'Numeric');
+ $context->operators->add(
+ '≠' => {
+ precedence => .5,
+ associativity => 'left',
+ type => 'bin',
+ string => ' != ',
+ TeX => '\ne ',
+ class => 'Inequalities::BOP::inequality',
+ eval => 'evalNotEqualTo'
+ }
+ );
+ $context->functions->set(sqrt => { class => 'restrictedDomains::Function::numeric' }); # override sqrt()
+ $context->functions->add(identity => { class => 'restrictedDomains::Function::numeric' });
+ $context->{cmpDefaults}{Formula}{checker} = sub {
+ my ($correct, $student, $ans) = @_;
+ return 0 if $ans->{isPreview} || $correct != $student;
+ $student = $ans->{student_formula};
+ $student = Formula("identity($student)"); #ensure student is parsed as Formula object
+ my $setSqrt = Context()->flag("setSqrt");
+ my $useBizarro = Context()->flag("useBizarro");
+ Context()->flags->set(
+ checkSqrt => $setSqrt,
+ bizarroAdd => $useBizarro,
+ bizarroSub => $useBizarro,
+ bizarroMul => $useBizarro,
+ bizarroDiv => $useBizarro
+ );
+ delete $correct->{test_values};
+ delete $student->{test_values};
+ my $OK = ($correct == $student);
+ Context()->flags->set(checkSqrt => 0, bizarroAdd => 0, bizarroSub => 0, bizarroMul => 0, bizarroDiv => 0);
+ Value::Error(Context()->flag('wrongFormMessage')) unless $OK;
+ return $OK;
+ };
+ $showPartialCorrectAnswers = 1;
+ $context->{cmpDefaults}{Formula}{showLengthHints} = 0;
+ $context->{cmpDefaults}{Formula}{list_checker} = sub {
+ my ($correct, $student, $ansHash, $value) = @_;
+ my $score = 0; # number of correct student answers
+ my @errors = (); # stores error messages
+ my $i; # loop counters
+ my $studentFormula = $ansHash->{student_formula};
+ # Student answer needs to be a List consisting of a Formula/Real and then 0 or more Union/Interval/Sets
+ if ($studentFormula->class ne 'Formula') {
+ push(@errors, "Your answer isn't an expression with a possibly restricted domain");
+ return (0, @errors);
+ }
+ if ($studentFormula->type ne 'Number' and $studentFormula->type ne 'List') {
+ push(@errors, "Your answer isn't an expression with a possibly restricted domain");
+ return (0, @errors);
+ }
+ my $studentList = $studentFormula;
+ if ($studentFormula->type eq 'Number') { $studentList = Compute("$studentFormula, (-inf,inf)"); }
+ # Check that first item in list is a Formula that returns a number
+ if (($studentList->value)[0]->type ne 'Number') {
+ push(@errors, "The first part of your answer is not an expression");
+ }
+ $domainsCount = scalar($studentList->value) - 1;
+ # Identify student answers like "1/x for x != 1, 2" and change to "1/x for x!=1, x!=2"
+ my $alteredStudentList = Formula(($studentList->value)[0] . ", " . ($studentList->value)[1]);
+ for $i (2 .. $domainsCount) {
+ my $dom = ($studentList->value)[$i];
+ my $prevdom = ($alteredStudentList->value)[ $i - 1 ];
+ if ($dom->type eq 'Number' and $prevdom->class eq 'Formula' and $prevdom->type eq 'Interval') {
+ if (defined $prevdom->{tree}->{bop}) {
+ if ($prevdom->{tree}->{bop} eq '!=') {
+ my $x = (Context()->variables->variables)[0];
+ $alteredStudentList = Formula($alteredStudentList->string . ", $x != $dom");
+ } else {
+ $alteredStudentList = Formula($alteredStudentList->string . ", $dom");
+ }
+ } else {
+ $alteredStudentList = Formula($alteredStudentList->string . ", $dom");
+ }
+ } else {
+ $alteredStudentList = Formula($alteredStudentList->string . ", $dom");
+ }
+ }
+ $studentList = $alteredStudentList;
+ for $i (1 .. $domainsCount) {
+ my $dom = ($studentList->value)[$i];
+ if ($dom->type ne 'Interval' and $dom->type ne 'Union' and $dom->type ne 'Set') {
+ push(@errors, "Unable to understand how '" . $dom->string . "' describes the domain");
+ return (0, @errors);
+ }
+ }
+ my $studentExpression = ($studentList->value)[0];
+ my $studentDomain = Interval("(-inf,inf)");
+ for my $i (1 .. $domainsCount) {
+ $studentDomain = $studentDomain->intersect(Interval(($studentList->value)[$i]->string));
+ }
+ $studentDomain = Compute("$studentDomain");
+
+ # Set correct answer parts
+ my $correctFormula = $ansHash->{correct_value};
+ my $correctScore = scalar($correctFormula->value);
+ my $correctExpression = ($correctFormula->value)[0];
+ my $correctDomain = ($correctFormula->value)[1];
+ $correctDomain = Compute("$correctDomain");
+ # If the correct answer has (-inf, inf) as the domain, don't print that in the correct answer
+ if ($correctDomain == Interval("(-inf,inf)")) {
+ $ansHash->{correct_ans_latex_string} = $correctExpression->TeX;
+ }
+ my $trueDomain = $correctDomain;
+ if (defined($correctFormula->{trueDomain})) { $trueDomain = $correctFormula->{trueDomain}; }
+
+ # Check math expression
+ my $expression_cmp = $correctExpression->cmp->evaluate($studentExpression->string);
+ my $expressionOK = $expression_cmp->{score};
+ push(@errors, 'Your expression is not correct')
+ unless ($expressionOK or $expression_cmp->{ans_message} or $ansHash->{isPreview});
+ push(@errors, $expression_cmp->{ans_message}) if $expression_cmp->{ans_message};
+
+ # Check student's domain
+ my $domainOK = 1;
+ my $trueUnion = Interval($trueDomain);
+ my $studentUnion = Interval($studentDomain);
+ my $correctUnion = Interval($correctDomain);
+ if (!$correctUnion->contains($studentUnion) or !$studentUnion->contains($trueUnion)) {
+ $domainOK = 0;
+ push(@errors, 'Your domain is not correct')
+ unless ($studentFormula->type eq 'Number' or $ansHash->{isPreview});
+ push(@errors, 'You need to specify the restricted domain')
+ if ($studentFormula->type eq 'Number' and $expressionOK);
+ }
+
+ my $allCorrectScore =
+ ($ansHash->{student_formula}->type eq 'Number') ? 2 : scalar($ansHash->{student_value}->value);
+ my $expressionWeight = Context()->flag("expressionWeight");
+ $score += $expressionWeight * $allCorrectScore if $expressionOK;
+ $score += (1 - $expressionWeight) * $allCorrectScore if $domainOK;
+ #$score = $score + $domainsCount if $domainOK;
+
+ return ($score, @errors);
+ };
+}
+
+###########################
+#
+# Subclass the numeric functions
+#
+package restrictedDomains::Function::numeric;
+our @ISA = ('Parser::Function::numeric');
+
+#
+# Override sqrt() to return a special value times x when evaluated
+#
+sub sqrt {
+ my $self = shift;
+ my $x = shift;
+ my $value = $self->context->flag("checkSqrt");
+ return $value + 1 if $value && $x == 1; # force sqrt(1) to be incorrect
+ return $value + 1 if $value && $x == $value; # force sqrt(sqrt(x)) to be incorrect
+ return $value + 1 if $value && $x == $self->context->flag("checkRoot"); # force sqrt(root(n,x))) to be incorrect
+ return $value * $x if $value;
+ return $self->SUPER::sqrt($x);
+}
+
+sub identity {
+ my $self = shift;
+ my $x = shift;
+ return $x;
+}
diff --git a/macros/contextScientificNotation.pl b/macros/contexts/contextScientificNotation.pl
similarity index 100%
rename from macros/contextScientificNotation.pl
rename to macros/contexts/contextScientificNotation.pl
diff --git a/macros/contextString.pl b/macros/contexts/contextString.pl
similarity index 100%
rename from macros/contextString.pl
rename to macros/contexts/contextString.pl
diff --git a/macros/contextTF.pl b/macros/contexts/contextTF.pl
similarity index 100%
rename from macros/contextTF.pl
rename to macros/contexts/contextTF.pl
diff --git a/macros/contextTrigDegrees.pl b/macros/contexts/contextTrigDegrees.pl
similarity index 100%
rename from macros/contextTrigDegrees.pl
rename to macros/contexts/contextTrigDegrees.pl
diff --git a/macros/contextTypeset.pl b/macros/contexts/contextTypeset.pl
similarity index 100%
rename from macros/contextTypeset.pl
rename to macros/contexts/contextTypeset.pl
diff --git a/macros/MathObjects.pl b/macros/core/MathObjects.pl
similarity index 100%
rename from macros/MathObjects.pl
rename to macros/core/MathObjects.pl
diff --git a/macros/PGML.pl b/macros/core/PGML.pl
similarity index 100%
rename from macros/PGML.pl
rename to macros/core/PGML.pl
diff --git a/macros/PGanswermacros.pl b/macros/core/PGanswermacros.pl
similarity index 100%
rename from macros/PGanswermacros.pl
rename to macros/core/PGanswermacros.pl
diff --git a/macros/PGauxiliaryFunctions.pl b/macros/core/PGauxiliaryFunctions.pl
similarity index 100%
rename from macros/PGauxiliaryFunctions.pl
rename to macros/core/PGauxiliaryFunctions.pl
diff --git a/macros/PGbasicmacros.pl b/macros/core/PGbasicmacros.pl
similarity index 100%
rename from macros/PGbasicmacros.pl
rename to macros/core/PGbasicmacros.pl
diff --git a/macros/PGcommonFunctions.pl b/macros/core/PGcommonFunctions.pl
similarity index 100%
rename from macros/PGcommonFunctions.pl
rename to macros/core/PGcommonFunctions.pl
diff --git a/macros/PGessaymacros.pl b/macros/core/PGessaymacros.pl
similarity index 100%
rename from macros/PGessaymacros.pl
rename to macros/core/PGessaymacros.pl
diff --git a/macros/PGgraders.pl b/macros/core/PGgraders.pl
similarity index 100%
rename from macros/PGgraders.pl
rename to macros/core/PGgraders.pl
diff --git a/macros/PGlateximage.pl b/macros/core/PGlateximage.pl
similarity index 100%
rename from macros/PGlateximage.pl
rename to macros/core/PGlateximage.pl
diff --git a/macros/PGsequentialmacros.pl b/macros/core/PGsequentialmacros.pl
similarity index 100%
rename from macros/PGsequentialmacros.pl
rename to macros/core/PGsequentialmacros.pl
diff --git a/macros/PGstandard.pl b/macros/core/PGstandard.pl
similarity index 100%
rename from macros/PGstandard.pl
rename to macros/core/PGstandard.pl
diff --git a/macros/core/PeriodicRerandomization.pl b/macros/core/PeriodicRerandomization.pl
new file mode 100644
index 0000000000..c2bdbc38f9
--- /dev/null
+++ b/macros/core/PeriodicRerandomization.pl
@@ -0,0 +1,176 @@
+
+=head1 NAME
+
+PeriodicRerandomization.pl - forced re-randomization of the
+problem every p submissions.
+
+=head1 SYNOPSIS
+
+Let p be a positive integer. This macro provides the ability
+to force a re-randomization of the question every p attempts.
+For example, this macro can be used to create a practice exercise
+that provides a new version of the question every 4 attempts
+and displays a solution every fourth attempt.
+
+=head1 DESCRIPTION
+
+Usage:
+
+ DOCUMENT();
+ loadMacros(
+ "PGstandard.pl",
+ "MathObjects.pl",
+ "PeriodicRerandomization.pl",
+ );
+ TEXT(beginproblem());
+
+ PeriodicRerandomization("4");
+
+ $a = random(2,9,1);
+ do { $b = random(2,9,1); } until ($b != $a);
+
+ BEGIN_TEXT
+ An equation for a line in the xy-plane with slope \( $a \)
+ and y-intercept \( $b \) is
+ $BR
+ $BR
+ \( y = \) \{ ans_rule(20) \}
+ $BR
+ $BR
+ END_TEXT
+
+ PeriodicStatus(
+ "Generate a new version of this problem",
+ "You have ${attempts_modp_remaining} attempt(s) remaining
+ before you will receive a new version of this problem."
+ );
+
+ if ($attempts_modp == 0) {
+ BEGIN_TEXT
+ $BR
+ $BR
+ ${BBOLD}Solution:${EBOLD} Multiply \( x \) by \( $a \)
+ and add \( $b \).
+ $BR
+ $BR
+ END_TEXT
+ }
+
+ ANS( Compute("$a x + $b")->cmp() );
+
+ COMMENT('Periodically re-randomizes.');
+
+ ENDDOCUMENT();
+
+
+The argument of C is a number p (the period)
+that is the number of attempts allowed before the problem is
+re-randomized. C must be called before
+any random parameters are defined.
+
+The two arguments of C are (1) the text that will
+appear on the button for a new version of the problem when the number
+of attempts is 0 mod p, and (2) a message that will appear when the
+number of attempts is not 0 mod N. If C has no
+arguments, then it will use defaults.
+
+There are several globally defined variables. C<$rerand_period> is
+the period p. C<$attempts_modp> is the total number of attempts modulo
+the period except for when the problem is first accessed, in which case
+its value is -p (the rationale behind this is that the case when the
+problem is first accessed often needs to be handled separately.)
+C<$attempts_modp_remaining> is the number of attempts
+remaining before re-randomization. C<$problem_version_number> is the
+floor function applied to the total number of attempts divided by the
+period p. C<$newProblemSeed> is the problem seed for the current
+version of the problem, whereas C<$problemSeed> is the original
+problem seed (which does not change with different versions of the
+problem). You can access the total number of attempts using
+C<$envir{numOfAttempts}>, which is a sequence 0,0,1,2,3,... (not
+0,1,2,3,...).
+
+=head1 AUTHOR
+
+Paul Pearson, Fort Lewis College, Department of Mathematics
+
+=cut
+
+###########################
+
+sub _PeriodicRerandomization_init { }; # don't reload this file
+
+sub PeriodicRerandomization {
+
+ #
+ # define some global variables
+ #
+
+ $rerand_period = shift;
+ $attempts_modp = ($envir{numOfAttempts} + 1) % $rerand_period;
+ $attempts_modp_remaining = $rerand_period - $attempts_modp;
+
+ #
+ # a correction because the $envir{numOfAttempts} progression is 0,0,1,2,3... instead of 0,1,2,3,...
+ #
+ if ($envir{numOfAttempts} == 0 && !defined(${$inputs_ref}{'hasBeenAttempted'})) {
+
+ TEXT(MODES(
+ HTML => qq! !,
+ HTML_dpng => qq! !,
+ TeX => ""
+ ));
+
+ $attempts_modp += -(1 + $rerand_period);
+ $attempts_modp_remaining += 1;
+
+ } else {
+
+ TEXT(MODES(
+ HTML => qq! !,
+ HTML_dpng => qq! !,
+ TeX => ""
+ ));
+
+ }
+
+ $problem_version_number = floor($envir{'numOfAttempts'} / $rerand_period);
+
+ if ($problemSeed < 2000) {
+ $newProblemSeed = $problemSeed + $problem_version_number;
+ } else {
+ $newProblemSeed = $problemSeed - $problem_version_number;
+ }
+
+ $main::PG_random_generator->srand($newProblemSeed);
+
+}
+
+sub PeriodicStatus {
+
+ my $button_text = shift;
+ my $status_text = shift;
+
+ if (!defined($button_text)) { $button_text = "Generate a new version of this problem"; }
+
+ if (!defined($status_text)) {
+ $status_text =
+ "You have ${attempts_modp_remaining} attempt(s) remaining before you will receive a new version of this problem.";
+ }
+
+ if ($attempts_modp == 0 && defined(${$inputs_ref}{'hasBeenAttempted'})) {
+
+ TEXT(MODES(
+ HTML => " ",
+ HTML_dpng => " ",
+ TeX => ""
+ ));
+
+ } else {
+
+ TEXT(EV3($status_text));
+
+ }
+
+}
+
+1;
diff --git a/macros/RserveClient.pl b/macros/core/RserveClient.pl
similarity index 100%
rename from macros/RserveClient.pl
rename to macros/core/RserveClient.pl
diff --git a/macros/Value.pl b/macros/core/Value.pl
similarity index 100%
rename from macros/Value.pl
rename to macros/core/Value.pl
diff --git a/macros/compoundProblem.pl b/macros/core/compoundProblem.pl
similarity index 100%
rename from macros/compoundProblem.pl
rename to macros/core/compoundProblem.pl
diff --git a/macros/compoundProblem2.pl b/macros/core/compoundProblem2.pl
similarity index 100%
rename from macros/compoundProblem2.pl
rename to macros/core/compoundProblem2.pl
diff --git a/macros/compoundProblem5.pl b/macros/core/compoundProblem5.pl
similarity index 100%
rename from macros/compoundProblem5.pl
rename to macros/core/compoundProblem5.pl
diff --git a/macros/problemRandomize.pl b/macros/core/problemRandomize.pl
similarity index 98%
rename from macros/problemRandomize.pl
rename to macros/core/problemRandomize.pl
index 0da080bec3..1a2288ee8e 100644
--- a/macros/problemRandomize.pl
+++ b/macros/core/problemRandomize.pl
@@ -180,11 +180,8 @@ sub new {
when => "correct",
onlyAfterDue => 1,
style => "Button",
- styleName => (
- defined $main::inputs_ref->{effectiveUser}
- && defined $main::inputs_ref->{user}
- && $main::inputs_ref->{effectiveUser} ne $main::inputs_ref->{user}
- ) ? "checkAnswers" : "submitAnswers",
+ styleName =>
+ ($main::inputs_ref->{effectiveUser} ne $main::inputs_ref->{user} ? "checkAnswers" : "submitAnswers"),
label => undef,
buttonLabel => $main::PG->maketext("Get a new version of this problem"),
checkboxLabel => $main::PG->maketext("Get a new version of this problem"),
diff --git a/macros/sage.pl b/macros/core/sage.pl
similarity index 100%
rename from macros/sage.pl
rename to macros/core/sage.pl
diff --git a/macros/scaffold.pl b/macros/core/scaffold.pl
similarity index 100%
rename from macros/scaffold.pl
rename to macros/core/scaffold.pl
diff --git a/macros/weightedGrader.pl b/macros/core/weightedGrader.pl
old mode 100755
new mode 100644
similarity index 100%
rename from macros/weightedGrader.pl
rename to macros/core/weightedGrader.pl
diff --git a/macros/deprecated/Alfredmacros.pl b/macros/deprecated/Alfredmacros.pl
new file mode 100755
index 0000000000..78ce2a9d0f
--- /dev/null
+++ b/macros/deprecated/Alfredmacros.pl
@@ -0,0 +1,376 @@
+# A group of macros used in the Alfred problem library.
+
+sub _Alfredmacros_init { } #don't reload these macros.
+
+# Given a point (x,y) this macro computes the angle with respect to x-axis. The angle will be between 0 and 2pi.
+sub inversetrig {
+ my $refangle = arctan(abs($_[1] / $_[0]));
+ if ($_[0] == 0) {0}
+ elsif ($_[0] > 0) {
+ if ($_[1] == 0) {0}
+ elsif ($_[1] > 0) { $refangle; }
+ else { 2 * pi- $refangle; }
+ } else {
+ if ($_[1] == 0) {pi}
+ elsif ($_[1] > 0) { pi- $refangle; }
+ else { pi + $refangle }
+ }
+}
+
+## Compute the max and min of an array of numbers
+
+#This macro prevents students from double clicking in an answer box. This macro #is necessary for multiple integral problems where the answer box is typeset
+#into the integration symbols.
+sub doubleclickprevent {
+ TEXT(MODES(
+ TeX => "",
+ HTML => ""
+ ));
+}
+
+#The problems that have the answer box in the limits must be displayed in JS
+#math mode. This macro warns the user to use JS math mode if they are not.
+sub jsMathwarn {
+ TEXT(MODES(
+ TeX => '',
+ HTML_jsMath => '',
+ HTML => $HR
+ . "Warning: to use this problem, you need to"
+ . "select jsMath mode in the Display Options panel at the left"
+ . $HR,
+ ));
+}
+
+sub jsmathmode {
+ TEXT(MODES(
+ TeX => '',
+ HTML_jsMath => '',
+ HTML => $HR
+ . "Warning: to use this problem, you need to "
+ . "select jsMath mode in the Display Options panel at the left"
+ . $HR,
+ ));
+ TEXT(MODES(
+ TeX => "",
+ HTML => ""
+ ));
+
+}
+
+sub mathjaxmode {
+ TEXT(MODES(
+ TeX => '',
+ HTML_MathJax => '',
+ HTML => $HR
+ . "Warning: to use this problem, you need to "
+ . "select MathJax mode in the Display Options panel at the left"
+ . $HR,
+ ));
+}
+
+#This subroutine includes the Strang's textbook into a problem, you have to
+#feed it the chapter and section. It is assumed that the book is in the course
+#directory and is labeled strangtextbook. The parameters are
+#strang(chapter,section,(optional) section title.
+#example \{&strang(16,5,"surface integrals")\}.
+$wwstrang = "http://webwork.alfred.edu/webwork2_course_files/strangcalculus";
+
+sub strang {
+ htmlLink(
+ qq!$wwstrang/Strang-$_[0]-$_[1].pdf!,
+ "$_[0].$_[1] $_[2] from Gilbert Strang's Calculus",
+ q/TARGET="new_window"/
+ );
+}
+
+#Inserts a link to a trig table in the problem.
+#example \{&trig_table()\}
+sub trig_table {
+ htmlLink(qq!$wwstrang/trig_identities.pdf!, "Trig Identities", q/TARGET="new_window"/);
+}
+
+sub strang_index {
+ htmlLink(qq!$wwstrang/Index.pdf!, "Index", q/TARGET="new_window"/);
+}
+
+sub strang_TOC {
+ htmlLink(qq!$wwstrang/TOC.pdf!, "Table of Contents", q/TARGET="new_window"/);
+}
+
+sub product_Rule_cmp {
+ my ($correct, $student, $self) = @_;
+ my ($f1stu, $f2stu, $f3stu, $f4stu) = @{$student};
+ my ($f1, $f2, $f3, $f4) = @{$correct};
+ my @fgrade = (0, 0, 0, 0);
+ my @fstu = ($f1stu, $f2stu, $f3stu, $f4stu);
+ my @fcorrect = ($f1, $f2, $f3, $f4);
+#we will associate each student answer with a prime number, noting which student answer is in which blank. This allows us to make use of the fundamental theorem of arithmetic.
+ my @prime = (2, 3, 5, 7);
+ my @answerblank = (0, 0, 0, 0);
+
+ for ($i = 0; $i < 4; $i++) {
+ for ($j = 0; $j < 4; $j++) {
+ if (($fcorrect[$i] == $fstu[$j]) && ($answerblank[$j] == 0)) { {
+ $answerblank[$j] = $prime[$i];
+ $j =
+ 4 # you have to terminate the inner loop in this case for the special case of f=e^x where f and f' are the same.
+ } }
+ }
+ }
+ for ($i = 0; $i < 4; $i++) {
+ if (!$answerblank[$i]) {
+ $self->setMessage($i + 1,
+ "All of your answers should be $f, $g, or a derivative of one of these functions");
+ }
+ }
+ #now we rely on the fact that products of primes are unique. First we check to see if all of the blanks are correct
+ if ((($answerblank[0] * $answerblank[1] == 6) && ($answerblank[2] * $answerblank[3] == 35))
+ || (($answerblank[0] * $answerblank[1] == 35) && ($answerblank[2] * $answerblank[3] == 6)))
+ {
+ @fgrade = (1, 1, 1, 1);
+ }
+ #now check to see if the first pair of blanks is correct, knowing one pair is not
+ elsif ($answerblank[0] * $answerblank[1] == 6) {
+ if (($answerblank[2] == 5) || ($answerblank[2] == 7)) {
+ @fgrade = (1, 1, 1, 0);
+ } elsif (($answerblank[3] == 5) || ($answerblank[3] == 7)) {
+ @fgrade = (1, 1, 0, 1);
+ } else {
+ @fgrade = (1, 1, 0, 0);
+ }
+ } elsif ($answerblank[0] * $answerblank[1] == 35) {
+ if (($answerblank[2] == 2) || ($answerblank[2] == 3)) {
+ @fgrade = (1, 1, 1, 0);
+ } elsif (($answerblank[3] == 2) || ($answerblank[3] == 3)) {
+ @fgrade = (1, 1, 0, 1);
+ } else {
+ @fgrade = (1, 1, 0, 0);
+ }
+ }
+ #if both sets are not correct, and the first set is not correct, check to see if the last pair are
+ elsif ($answerblank[2] * $answerblank[3] == 6) {
+ if (($answerblank[0] == 5) || ($answerblank[0] == 7)) {
+ @fgrade = (1, 0, 1, 1);
+ } elsif (($answerblank[1] == 5) || ($answerblank[1] == 7)) {
+ @fgrade = (0, 1, 1, 1);
+ } else {
+ @fgrade = (0, 0, 1, 1);
+ }
+ } elsif ($answerblank[2] * $answerblank[3] == 35) {
+ if (($answerblank[0] == 2) || ($answerblank[0] == 3)) {
+ @fgrade = (1, 0, 1, 1);
+ } elsif (($answerblank[1] == 2) || ($answerblank[1] == 3)) {
+ @fgrade = (0, 1, 1, 1);
+ } else {
+ @fgrade = (0, 0, 1, 1);
+ }
+ }
+#at this point they don't have a matched set of blanks correct. look for a single function in each pair that is right. You have to make sure you only get one for each pair of answer blanks.
+ else {
+ if (($answerblank[0])
+ && ($answerblank[2])
+ && ($answerblank[0] * $answerblank[2] != 6)
+ && ($answerblank[0] * $answerblank[2] != 35)
+ && ($answerblank[0] != $answerblank[2]))
+ {
+ @fgrade = (1, 0, 1, 0);
+ } elsif (($answerblank[0])
+ && ($answerblank[3])
+ && ($answerblank[0] * $answerblank[3] != 6)
+ && ($answerblank[0] * $answerblank[3] != 35)
+ && ($answerblank[0] != $answerblank[3]))
+ {
+ @fgrade = (1, 0, 0, 1);
+ } elsif (($answerblank[1])
+ && ($answerblank[2])
+ && ($answerblank[1] * $answerblank[2] != 6)
+ && ($answerblank[1] * $answerblank[2] != 35)
+ && ($answerblank[1] != $answerblank[2]))
+ {
+ @fgrade = (0, 1, 1, 0);
+ } elsif (($answerblank[1])
+ && ($answerblank[3])
+ && ($answerblank[1] * $answerblank[3] != 6)
+ && ($answerblank[1] * $answerblank[3] != 35)
+ && ($answerblank[1] != $answerblank[3]))
+ {
+ @fgrade = (0, 1, 0, 1);
+ } elsif ($answerblank[0]) {
+ @fgrade = (1, 0, 0, 0);
+ } elsif ($answerblank[1]) {
+ @fgrade = (0, 1, 0, 0);
+ } elsif ($answerblank[2]) {
+ @fgrade = (0, 0, 1, 0);
+ } elsif ($answerblank[3]) {
+ @fgrade = (0, 0, 0, 1);
+ }
+ }
+ return [@fgrade];
+}
+
+sub check_boundary_conditions {
+ my ($correct, $student, $self) = @_;
+ return product_Rule_cmp(@_);
+}
+
+sub Snxy() {
+ my %args = @_;
+ my @x = @{ $args{inputs} };
+ my @y = @{ $args{outputs} };
+ my $m = $args{m};
+ my $n = $args{n};
+ my $i = 0;
+ my $sum = 0;
+ if ($#x == $#y) {
+ for ($i = 0; $i <= $#x; $i++) {
+ $sum = $sum + ($x[$i])**($n) * ($y[$i])**($m);
+ }
+ } else {
+ $sum = 0;
+ }
+ return $sum;
+}
+
+### To use the macros your problem must include unionTables.pl, and of course Alfredmacros.pl
+### Table integral returns a string that can be included in a Table to output an integral whose upper and lower limits
+### of integration can be answer blanks. There are several optional parameters:
+### width - change the width of the answer blanks. defaults to 3.
+### lowerwidth - change the width of the lower answer blank. defaults to width
+### upperwidth - change the width of the upper answer blank. defaults to width.
+### upper - the uppper limit of integration, does not have to be an answer blank, defaults to answer blank with width "width"
+### lower - the lower limit of integration, does not have to be an answer blank, defaults to answer blank with width "width"
+### limits - boolean, if 1 puts the limits of integration above and below the integral symbol, if 0 puts them after the integral symbol.
+### default is 1.
+### Your code must include unionTables.pl, and of course Alfredmacros.pl
+### An example:
+### \{BeginTable(center=>0).
+### Row([tableintegral(),
+### ],separation=>2).
+### EndTable();
+### \}
+### which will print an integral with answer blank on the upper and lower limits with the default length of 3
+###
+### This example prints out a double integral, the first integral with answer blanks with width 10, the second integral
+### has 0 for the lower limit of integration and an answer blank with width 5 for the upper limit of integration.
+### The default limits of integratin are answer blanks with width 3, in this case the default width was overridden to 5
+### and the default lower limit was changed to a zero.
+### \{BeginTable(center=>0).
+### Row([tableintegral(width=>10,limits=>'\(0\)'),tableintegral(width=>5,lower=>'\(0\)',limits=>0),
+### ],separation=>2).
+### EndTable();
+### \}
+### An example where the width of the upper and lower answer blanks have different widths.
+### \{BeginTable(center=>0).
+### Row([tableintegral(lowerwidth=>10,upperwidth=>1)
+### ],separation=>2).
+### EndTable();
+### \}
+
+sub tableintegral {
+ my %arg = @_;
+ my $width = delete $arg{width} // 3;
+ my $lowerwidth = delete $arg{lowerwidth} // $width;
+ my $upperwidth = delete $arg{upperwidth} // $width;
+ my $lower = delete $arg{lower} // ans_rule($lowerwidth);
+ my $upper = delete $arg{upper} // ans_rule($upperwidth);
+ my $limits = delete $arg{limits} // 1;
+ if ($limits == 1) {
+ return $upper . $BR . '\(\displaystyle\int\)' . $BR . $lower;
+ } else {
+ return '\(\displaystyle\int\)', $upper . $BR . $BR . $lower;
+ }
+}
+
+# a sum with answer blanks for the summation variable, lower limit, and upper limit
+#\{ BeginTable(center=>0).
+# Row([tablesum(width=>10),
+# ],separation=>2).
+# EndTable();
+#\}
+# a sum with answer blanks for the upper and lower limits, and the summation variable is i
+#\{ BeginTable(center=>0).
+# Row([tablesum(width=>10,sumvariable=>'i'),
+# ],separation=>2).
+# EndTable();
+#\}
+# a sum with answer blanks for the upper and lower limits, and summation variable is not used.
+#\{ BeginTable(center=>0).
+# Row([tablesum(width=>10,usesumvariable=>0),
+# ],separation=>2).
+# EndTable();
+#\}
+# sum from n = 1 to infinity
+#\{ BeginTable(center=>0).
+# Row([tablesum(sumvariable=>'\(n\)',lower=>'\(1\)', upper=>'\(\hskip 3pt\infty\)') ],separation=>2).
+# EndTable();
+#\}
+
+sub tablesum {
+ my %arg = @_;
+ my $width = delete $arg{width} // 3;
+ my $lowerwidth = delete $arg{lowerwidth} // $width;
+ my $upperwidth = delete $arg{upperwidth} // $width;
+ my $lower = delete $arg{lower} // ans_rule($lowerwidth);
+ my $upper = delete $arg{upper} // ans_rule($upperwidth);
+ my $limits = delete $arg{limits} // 1;
+ my $sumvariable = delete $arg{sumvariable} // ans_rule($width);
+ my $usesumvariable = delete $arg{usesumvariable} // 1;
+
+ if ($usesumvariable == 0) {
+ if ($limits == 1) {
+ return $upper . $BR . '\(\displaystyle\sum\)' . $BR . $lower;
+ } else {
+ return '\(\displaystyle\int\)', $upper . $BR . $BR . $lower;
+ }
+ } else {
+ if ($limits == 1) {
+ return $upper . $BR . '\(\displaystyle\sum\)' . $BR . $sumvariable . '\( = \)' . $lower;
+ } else {
+ return '\(\displaystyle\int\)', $upper . $BR . $BR . $sumvariable . '\( = \)' . $lower;
+ }
+ }
+}
+
+### Create a vertical bar with an upper and lower limit.
+sub tableevaluate {
+ my %arg = @_;
+ my $width = delete $arg{width} // 3;
+ my $lowerwidth = delete $arg{lowerwidth} // $width;
+ my $upperwidth = delete $arg{upperwidth} // $width;
+ my $lower = delete $arg{lower} // ans_rule($lowerwidth);
+ my $upper = delete $arg{upper} // ans_rule($upperwidth);
+ return '\(\Bigg\vert\)', $upper . $BR . $BR . $lower;
+}
+
+### Create a subscripted character
+sub tablesubscript {
+ my %arg = @_;
+ my $width = delete $arg{width} // 3;
+ my $lower = delete $arg{lower} // ans_rule($width);
+ my $variable = delete $arg{variable} // 'c';
+ return $variable, $BR . $BR . $lower;
+}
+
+### Create a superscripted character
+sub tablesuperscript {
+ my %arg = @_;
+ my $width = delete $arg{width} // 3;
+ my $upper = delete $arg{upper} // ans_rule($width);
+ my $variable = delete $arg{variable} // 'c';
+ return $variable, $upper . $BR . $BR . $BR;
+}
+
+### A fraction
+sub tablefrac {
+ my %arg = @_;
+ my $width = delete $arg{width} // 3;
+ my $lower = delete $arg{lower} // ans_rule($width);
+ my $upper = delete $arg{upper} // ans_rule($width);
+ my $barwidth = delete $arg{barwidth} // 10 + $width;
+ my $divisionbar = "";
+ for ($count = 1; $count <= $barwidth; $count++) {
+ $divisionbar = $divisionbar . "-";
+ }
+ return $upper . $BR . $divisionbar . $BR . $lower;
+}
+
diff --git a/macros/deprecated/BrockPhysicsMacros.pl b/macros/deprecated/BrockPhysicsMacros.pl
new file mode 100644
index 0000000000..61c5d1a210
--- /dev/null
+++ b/macros/deprecated/BrockPhysicsMacros.pl
@@ -0,0 +1 @@
+# this file defines all Brock-Physics-specific macros.
diff --git a/macros/deprecated/CofIdaho_macros.pl b/macros/deprecated/CofIdaho_macros.pl
new file mode 100755
index 0000000000..351fec2427
--- /dev/null
+++ b/macros/deprecated/CofIdaho_macros.pl
@@ -0,0 +1,723 @@
+
+=pod
+
+=head1 NAME
+
+ extra macros for Intermediate Algebra problems at The College of Idaho
+
+=head1 Synposis
+ macros by R Cruz -- The College of Idaho
+=cut
+
+=head3 Format the instuctor answer
+
+=pod
+
+1) SimplifyExponents: Formats an expression without negative exponents
+ To use: SimplifyExponents(num, den,~~@var,@exp);
+ where num = numerator's constant
+ den = denominator's constant
+ ~~@var = pointer to an array with the list of variables
+ @exp = array with the exponents for the variables
+
+2) num_and_unit_checker: Checks for units like "apples" or "oranges"
+ To use: ANS (num_and_units_checker($correct_answer, "units");
+
+3) strict_percent_cmp: Checks percentages have been rounded as required.
+ To use: ANS(strict_percent_cmp($answer,"%",roundto));
+ where $answer is in percent form: xx.x
+ roundto = "tenths" or "hundredths"
+
+4) Picky_equation_cmp: Checks both sides of an equation model the word problem.
+ To use: Picky_equation_cmp("2x+1=5");
+ Note: This one allows only the variables that have been used in the CofIdaho
+ problems. It should be changed so that it is more flexible, or better,
+ convert it to a MathObject.
+
+5) SlopeIntercept_equation_cmp: Checks both sides of an equation for the
+ slope-intercept form of a line.
+ To use: ANS(SlopeIntercept_equation_cmp($answer));
+ where $answer = "y = $m x + $b"; (Use only y and x for variables.)
+
+6) functionNotation_cmp: Checks for "f(x)= ***" notation
+ To use:
+ ANS(functionNotation_cmp("f(x)=2x",["x","f"]));
+
+7) Checks factors of polynomials.
+ To use: The answer must be submitted in this form:
+ ANS(FactorEvaluator($answer,[variable array]));
+ If the polynomial is prime:
+ ANS(FactorEvaluator("Does not factor",polynomial,[variable array]));
+
+ Note: Answers must be in the form: monomial(poly)...(poly) with
+ or without parentheses about the monomial. The polynomial factors must
+ not contain any other grouping symbols and, in any case, only parenthesis
+ may be used. This needs to be in the instructions for any set that uses
+ this macro (in the screenHeader.pg file).
+
+ Note: "StrictFactoringEvaluator" requires leading negatives to be factored out.
+ This not may not work with all situations. BE SURE TO CHANGE THIS FILE IF THE
+ FactoringEvaluator IS CHANGED!!
+
+8) RationalExpEvaluator: Checks for simplified rational expressions.
+ To use: ANS(RationalExpEvaluation($answer,[variable array]));
+ or something like: ANS(RationalExpEvaluator($answer,["x","y"]));
+
+ Note: Answers must be of the form: (poly)/(poly)
+
+9) ReduceFraction: Returns a string that represents a reduced fraction.
+ To use: $a = SimplifyFraction(numerator expression,denominator espression);
+=cut
+
+###################################################################
+# 1) Formats an expression without negative exponents
+# Thanks to John Jones at ASU for this macro.
+
+sub SimplifyExponents {
+ loadMacros("contextLimitedPowers.pl");
+ my $num = shift;
+ my $den = shift;
+ my $varref = shift;
+ my @expos = @_;
+ my @vars = @$varref;
+
+ Context()->operators->set(@LimitedPowers::OnlyPositiveIntegers);
+
+ my $answer_n = Formula("$num");
+ my $answer_d = Formula("$den");
+
+ for $j (0 .. (scalar(@vars) - 1)) {
+ $answer_n *= Formula("$vars[$j]^$expos[$j] ") if ($expos[$j] > 0);
+ $answer_d *= Formula("$vars[$j]^(-1*$expos[$j]) ") if ($expos[$j] < 0);
+ }
+
+ $answer = $answer_n / $answer_d;
+
+ return ($answer->reduce());
+}
+
+sub Simplified {
+ my ($correct, $student, $ah) = @_;
+ return unless $ah->{score} == 1; #This does not work???
+ my $VariablesPlus1 = 2; #Put in the number of variables plus 1
+ my $check = $ah->{student_ans};
+ if (!($ah->{isPreview})
+ && $correct == $student
+ && $check =~ /(([abxy].*){$VariablesPlus1,})|([\Q^].*[\Q^])/)
+ {
+ $ah->{ans_message} = "Simplify your answer";
+ }
+
+ return ($correct == $student && ($ah->{student_ans} !~ /(([abxy].*){$VariablesPlus1,})|([\Q^].*[\Q^])/));
+}
+
+###################################################################
+# 2) Checks for units
+
+sub num_and_unit_checker {
+ my $correct_ans = shift; #the answer
+ my $correct_units = shift; #the unit type
+ my $old_evaluator = num_cmp($correct_ans);
+
+ my $new_evaluator = sub {
+ my $student_ans = shift;
+ my $formatted_ans = $student_ans;
+ $formatted_ans =~ s{$correct_units}{};
+ my $ans_hash = $old_evaluator->evaluate($formatted_ans);
+ if ($student_ans !~ /$correct_units/) {
+ $ans_hash->{score} = 0;
+ $ans_hash->setKeys('ans_message' => "Enter your answer with the correct units: $correct_units");
+ }
+ $ans_hash->{original_student_ans} = "$student_ans";
+ $ans_hash->{preview_latex_string} = "$student_ans";
+ $ans_hash->{student_ans} = "$student_ans";
+ $ans_hash->{correct_ans} = "$correct_ans" . " $correct_units";
+
+ return $ans_hash;
+ };
+
+ return $new_evaluator;
+}
+
+###################################################################
+# 3) Checks for rounded percents
+
+sub strict_percent_cmp {
+ my $correct_ans = shift; #the answer
+ my $correct_units = shift; #the unit type which should be "%"
+ my $error = shift; #what to round to
+ my $errorAmount = .1;
+ if ($error eq "hundredth") { $errorAmount = .01; }
+ my $old_evaluator = num_cmp($correct_ans, tol => 0);
+
+ my $new_evaluator = sub {
+ my $student_ans = shift;
+ my $formatted_ans = $student_ans;
+ $formatted_ans =~ s{$correct_units}{};
+ my $ans_hash = $old_evaluator->evaluate($formatted_ans);
+ if ($student_ans !~ /$correct_units/) {
+ $ans_hash->{score} = 0;
+ $ans_hash->setKeys('ans_message' => "Enter your answer with the correct units: $correct_units");
+ } else {
+ if ($ans_hash->{score} == 0
+ && abs($formatted_ans - $correct_ans) < $errorAmount)
+ {
+ $ans_hash->setKeys('ans_message' => "Round your answer to the nearest $error.");
+ }
+ }
+ $ans_hash->{original_student_ans} = "$student_ans";
+ $ans_hash->{preview_latex_string} = "$student_ans";
+ $ans_hash->{student_ans} = "$student_ans";
+ $ans_hash->{correct_ans} = "$correct_ans" . " $correct_units";
+
+ return $ans_hash;
+ };
+
+ return $new_evaluator;
+}
+
+###################################################################
+# 4) Checks both sides of an equation model the word problem.
+
+sub Picky_equation_cmp {
+
+ loadMacros("unionUtils.pl", "answerUtils.pl", "listAnswer.pl", "extraAnswerEvaluators.pl");
+
+ my $ans = shift;
+
+ my $new_evaluator = sub {
+ my $student = shift;
+
+ $ans =~ tr/[=]/,/;
+ $student =~ tr/[=]/,/;
+
+ my $old_evaluator = fun_list_cmp($ans,
+ vars => [ 'a', 'b', 'c', 'd', 'h', 'l', 'n', 'v', 'w', 'x', 'y', 's', 't', 'A', 'L', 'R', 'C', 'P' ]);
+ my $ans_hash_old = $old_evaluator->evaluate($student);
+
+ $ans =~ tr/,/=/;
+ $student =~ tr/,/=/;
+ $ans_hash_old->{correct_ans} = "$ans";
+ $ans_hash_old->{original_student_ans} = "$student";
+ $ans_hash_old->{student_ans} = "$student";
+ $ans_hash_old->{preview_latex_string} = "$student";
+ if ($ans_hash_old->{score} != 1) {
+ $ans_hash_old->{score} = 0;
+ # $ans_hash_old->setKeys( 'ans_message' =>"Your equation must model the problem.");
+ $ans_hash_old->setKeys('ans_message' => "At least one side is incorrect.");
+ }
+ if ($student !~ /[=]/) {
+ if ($ans =~ /[RCP]/) {
+ @side = split(/[=]/, $ans);
+ $ans_hash_old->setKeys('ans_message' => "Enter your answer in the form: $side[0] = expression.");
+ } else {
+ $ans_hash_old->setKeys('ans_message' => "You must enter an equation.");
+ }
+ }
+
+ return $ans_hash_old;
+ };
+ return $new_evaluator;
+}
+
+###################################################################
+# 5) Checks both sides of an equation for the slope-intercept form of a line.
+
+sub SlopeIntercept_equation_cmp {
+
+ loadMacros("unionUtils.pl", "answerUtils.pl", "contextLimitedPolynomial.pl");
+
+ my $ans = shift;
+ my $formatAns = $ans;
+ $formatAns =~ tr/ //;
+ my @correctSides = split(/[=]/, $formatAns);
+
+ my $new_evaluator = sub {
+ my $left_evaluator = fun_cmp($correctSides[0], vars => [ 'x', 'y' ]);
+ my $right_evaluator = fun_cmp($correctSides[1], vars => [ 'x', 'y' ]);
+
+ my $student = shift;
+ my $formatStudent = $student;
+ $formatStudent =~ tr/ //;
+ if ($student !~ /[=]/) {
+ my $ans_hash = $right_evaluator->evaluate($formatStudent);
+ $ans_hash->{correct_ans} = "$ans";
+ $ans_hash->{original_student_ans} = "$student";
+ $ans_hash->{student_ans} = "$student";
+ $ans_hash->{preview_latex_string} = "$student";
+ $ans_hash->setKeys('ans_message' => "You must enter an equation.");
+ $ans_hash->{score} = 0;
+ return $ans_hash;
+ } else {
+ my @studentSides = split(/[=]/, $formatStudent);
+
+ my $left_ans_hash = $left_evaluator->evaluate($studentSides[0]);
+ my $right_ans_hash = $right_evaluator->evaluate($studentSides[1]);
+
+ $left_ans_hash->{correct_ans} = "$ans";
+ $left_ans_hash->{original_student_ans} = "$student";
+ $left_ans_hash->{student_ans} = "$student";
+ $left_ans_hash->{preview_latex_string} = "$student";
+
+ if ($right_ans_hash->{score} == 0) { $left_ans_hash->{score} = 0; }
+
+ my $perlNumber = "[0-9]+[\.\/]?[0-9]*|\.[0-9]+";
+ my $BadForm =
+ ($studentSides[0] =~ /\w.*\w/
+ || $studentSides[1] =~ /[a-zA-Z].*[a-zA-Z]/
+ || $studentSides[1] =~ /($perlNumber)[+-]+($perlNumber[a-zA-Z][+-])?($perlNumber)/);
+ if ($BadForm) {
+ $left_ans_hash->setKeys(
+ 'ans_message' => "Enter your answer in the form: $BR y = mx+b, y = b or x = c");
+ $left_ans_hash->{score} = 0;
+ }
+
+ return $left_ans_hash;
+ }
+ };
+ return $new_evaluator;
+}
+
+###################################################################
+# 6) Checks for function notation in an equality.
+
+sub functionNotation_cmp {
+
+ loadMacros("unionUtils.pl", "answerUtils.pl");
+
+ my $ans = shift;
+ my @variables = @_;
+
+ my $formatAns = $ans;
+ $formatAns =~ tr/ //;
+ my @correctSides = split(/[=]/, $formatAns);
+
+ my $new_evaluator = sub {
+ # my $notation_evaluator = fun_cmp($correctSides[0],vars=>@variables);
+ my $notation_evaluator = ordered_cs_str_cmp($correctSides[0]);
+ my $notation2_evaluator = unordered_str_cmp($correctSides[0]);
+ my $formula_evaluator = fun_cmp($correctSides[1], vars => @variables, tol => .001);
+
+ my $student = shift;
+ my $formatStudent = $student;
+ $formatStudent =~ tr/ //;
+
+ if ($student !~ /[=]/ || $student !~ /(\w\s*\(\s*\w\s*\))/) {
+ my $ans_hash = $formula_evaluator->evaluate($formatStudent);
+ $ans_hash->{correct_ans} = "$ans";
+ $ans_hash->{original_student_ans} = "$student";
+ $ans_hash->{student_ans} = "$student";
+ $ans_hash->{preview_latex_string} = "$student";
+ $ans_hash->setKeys('ans_message' => "Your answer must be in function notation.");
+ return $ans_hash;
+ } else {
+ my @studentSides = split(/[=]/, $formatStudent);
+
+ my $notation_ans_hash = $notation_evaluator->evaluate($studentSides[0]);
+ my $notation2_ans_hash = $notation2_evaluator->evaluate($studentSides[0]);
+ my $formula_ans_hash = $formula_evaluator->evaluate($studentSides[1]);
+
+ $formula_ans_hash->{correct_ans} = "$ans";
+ $formula_ans_hash->{original_student_ans} = "$student";
+ $formula_ans_hash->{student_ans} = "$student";
+ $formula_ans_hash->{preview_latex_string} = "$student";
+
+ if ($notation_ans_hash->{score} != 1) {
+ $formula_ans_hash->{ans_message} = $notation_ans_hash->{ans_message};
+ if ($notation2_ans_hash->{score} == 1) {
+ $formula_ans_hash->setKeys(
+ 'ans_message' => "Your answer must be in correct function notation.");
+ }
+ $formula_ans_hash->{score} = 0;
+ }
+
+ return $formula_ans_hash;
+ }
+ };
+ return $new_evaluator;
+}
+
+###################################################################
+# 7) Checks factors of polynomials.
+# Note: Student's answers must be of the form: monomial(poly)...(poly) with
+# or without parentheses about the monomial. The polynomial factors must
+# not contain any other grouping symbols and, in any case, only parenthesis
+# may be used. This needs to be in the instructions for any set that uses
+# this macro (in the screenHeader.pg file).
+# Note: "StrictFactoringEvaluator" requires leading negatives to be factored out.
+# This not may not work with all situations. BE SURE TO CHANGE THIS FILE IF THE
+# FactoringEvaluator IS CHANGED!!
+#---------For diagnosing problems--------------------------------------
+# $ans_hash->setKeys( 'ans_message' =>"FYI: Not checking correctly.
+# AnswerHashScore: $ans_hash->{score} :
+# Student: $#student_factors, $student_ans,
+# Formatted: $format_student_ans,
+# Student Factors: $student_factors[0], $student_factors[1],
+# : Correct: $#factors, $factors[0], $factors[1],
+# Number matched: $CorrectFactors");
+#----------------------------------------------------------------------
+
+sub FactoringEvaluator {
+
+ Context()->strings->add("Does not factor" => ());
+
+ my $ans = shift;
+ my $ans_text = $ans; #For string answers like "Does not factor"
+ if ($ans =~ /factor/) {
+ $ans = shift;
+ }
+ my @vars = @_;
+
+ my $format_ans = $ans;
+ $format_ans =~ s/\*/ /g; #Remove any astrix
+ $format_ans =~ s/[\(]/,\(/g; #Put in the delimiter ,
+ if ($format_ans =~ /^,/) { $format_ans =~ s/,//; } #Remove any leading comma
+ my @factors = split(/[,]/, $format_ans); #Split off the terms
+ for $k (0 .. $#factors) { $factors[$k] = Formula($factors[$k])->reduce; }
+ $ans = Formula($ans)->reduce;
+
+ my $old_evaluator = fun_cmp($ans, var => @vars);
+
+ my $new_evaluator = sub {
+ local ($factor_eval, $negfactor_eval, $factor_hash, $negfactor_hash);
+
+ my $student_ans = shift;
+
+ #---------For string answers----------------------------------------------
+ if ($student_ans =~ /factor/) {
+ my $old_eval_string = str_cmp($ans_text);
+ my $ans_hash = $old_eval_string->evaluate($student_ans);
+ $ans_hash->{correct_ans} = $ans_text; #For better display (no caps)
+ $ans_hash->{student_ans} = $student_ans;
+ $ans_hash->{original_student_ans} = $student_ans;
+ return $ans_hash;
+ }
+ #---------------------------------------------------------------------------
+
+ my $ans_hash = $old_evaluator->evaluate($student_ans);
+
+ #----------The only parentheses are allowed for grouping symbols------------
+ #----------and only the minimum set needed to enter the answer correctly.---
+ if ($student_ans =~ /[\{\}\[\]]/) {
+ $ans_hash->{student_ans} = $student_ans;
+ $ans_hash->{original_student_ans} = $student_ans;
+ $ans_hash->{score} = 0;
+ $ans_hash->setKeys('ans_message' => "You may only use parentheses in your answer.");
+ return $ans_hash;
+ }
+ if ($student_ans =~ /[\(][^\)]*[\(]/) {
+ $ans_hash->{student_ans} = $student_ans;
+ $ans_hash->{original_student_ans} = $student_ans;
+ $ans_hash->{score} = 0;
+ $ans_hash->setKeys('ans_message' => "Your answer has extraneous grouping symbols.");
+ return $ans_hash;
+ }
+
+ #-----------Check the factors---------------------------------------------
+
+ if ($ans_hash->{score} == 1) {
+ my $format_student_ans = $student_ans;
+ $format_student_ans =~ s/[\*]/ /g; #Remove any astrix
+ $format_student_ans =~ s/[\(]/,\(/g; #Put in the delimiter ,
+ if ($format_student_ans =~ /^,/) { $format_student_ans =~ s/,//; } #Remove leading commas
+ my @student_factors = split(/[,]/, $format_student_ans); #Split off the terms
+ for $k (0 .. $#student_factors) { $student_factors[$k] = Formula($student_factors[$k]); }
+
+ #########Could this be done with a list?--would have to change the list's error msg.
+
+ my $CorrectFactors = 0;
+
+ foreach $i (0 .. $#factors) {
+ $factor_eval = fun_cmp($factors[$i], var => @vars);
+ $negfactor_eval = fun_cmp(-1 * ($factors[$i]), var => @vars);
+ foreach $j (0 .. $#student_factors) {
+ $factor_hash = $factor_eval->evaluate($student_factors[$j]);
+ $negfactor_hash = $negfactor_eval->evaluate($student_factors[$j]);
+ if ($factor_hash->{score} == 1 || $negfactor_hash->{score} == 1) {
+ $CorrectFactors = $CorrectFactors + 1;
+
+ #---------This segment is for binomial factors raised to powers----------------
+ # Example: (3x+1)^2 is correct, and (9x^2+6x+1) should not be accepted
+ # SHOULD MAKE THIS MORE GENERAL: TAKE OTHER EXPONENTS BESIDES INTEGERS,
+ # THE PATTERN SHOULD BE BETTER DEFINED.
+ #------------------------------------------------------------------------------
+ $factor_string = $factors[$i]->string;
+ $st_factor_string = $student_factors[$j]->string;
+ if ($factor_string =~ /\(.*[+-]{1}.*\)\^\d+$/
+ && $st_factor_string !~ /\(.*[+-]{1}.*\)\^\d+$/)
+ {
+ $ans_hash->{score} = 0;
+ $ans_hash->setKeys('ans_message' => "Factor the expression completely.");
+ }
+ #-------------------------------------------------------------------------------
+ }
+ }
+ }
+
+ if ($CorrectFactors != $#factors + 1) {
+ $ans_hash->{score} = 0;
+ $ans_hash->setKeys(
+ 'ans_message' => "Check that your answer is factored
+ completely and is entered in the
+ required format."
+ );
+ }
+ }
+
+ if ($ans_text =~ /factor/) { $ans_hash->{correct_ans} = $ans_text; } #In case the answer is a string
+
+ return $ans_hash;
+ }; #END of SUB
+
+ Context()->strings->remove("Does not factor" => ());
+ return $new_evaluator;
+}
+
+sub StrictFactoringEvaluator {
+
+ my $ans = shift;
+ my @vars = @_;
+
+ my $format_ans = $ans;
+ $format_ans =~ s/[\*]//g; #Remove any astrix
+ $format_ans =~ s/[\(]/,\(/g; #Put in the delimiter ,
+ if ($format_ans =~ /^,/) { $format_ans =~ s/,//; } #Remove any leading commas
+ my @factors = split(/[,]/, $format_ans); #Split off the terms
+ for $k (0 .. $#factors) { $factors[$k] = Formula($factors[$k]); }
+ $ans = Formula($ans)->reduce;
+
+ my $old_evaluator = fun_cmp($ans, var => @vars);
+
+ my $new_evaluator = sub {
+ local ($factor_eval, $factor_hash);
+
+ my $student_ans = shift;
+
+ #----This could be changed so that it would check prime polyomials here instead
+ #----of in the problem template.
+ if ($student_ans =~ /factor/) {
+ my $string_ans = $ans->string;
+ my $old_eval_string = std_cs_str_cmp($string_ans);
+ my $ans_hash = $old_eval_string->evaluate($student_ans);
+ return $ans_hash;
+ }
+ #------------------------------------------------------
+
+ my $ans_hash = $old_evaluator->evaluate($student_ans);
+
+ if ($ans_hash->{score} == 1) { #Check factors
+ my $format_student_ans = $student_ans;
+ $format_student_ans =~ s/[\*]//g; #Remove any astrix
+ $format_student_ans =~ s/[\(]/,\(/g; #Put in the delimiter ,
+ if ($format_student_ans =~ /^,/) { $format_student_ans =~ s/,//; } #Remove any leading commas
+ my @student_factors = split(/[,]/, $format_student_ans); #Split off the terms
+ for $k (0 .. $#student_factors) { $student_factors[$k] = Formula($student_factors[$k]); }
+
+ #########Could this be done with a list?--would have to change the list's error msg.
+
+ my $CorrectFactors = 0;
+
+ foreach $i (0 .. $#factors) {
+ $factor_eval = fun_cmp($factors[$i], var => @vars);
+ foreach $j (0 .. $#student_factors) {
+ $factor_hash = $factor_eval->evaluate($student_factors[$j]);
+ if ($factor_hash->{score} == 1) {
+ $CorrectFactors = $CorrectFactors + 1;
+ }
+ }
+ }
+
+ if ($CorrectFactors != $#factors + 1) {
+ $ans_hash->{score} = 0;
+ $ans_hash->setKeys('ans_message' =>
+ "Check that your answer is factored completely and is entered in the required format.");
+ }
+ }
+ return $ans_hash;
+ };
+ return $new_evaluator;
+}
+
+###################################################################
+# 8) Checks for simplified rational expressions.
+# Note: Student's answers must be of the form: (poly)/(poly)
+#
+#---------For diagnosing problems--------------------------------------
+# $ans_hash->setKeys( 'ans_message' =>"FYI: Not checking correctly.
+# Student: $student_num, $student_den,$student_ans;
+# Answer: $num,$den,$ans");
+#----------------------------------------------------------------------
+
+sub RationalExpEvaluator {
+
+ Context()->strings->add("Does not simplify" => ());
+
+ my $ans = shift;
+ my $ans_text = $ans; #Mainly for string answers like "Does not simplify"
+ if ($ans =~ /not/) {
+ $ans = shift;
+ }
+ my @vars = @_;
+
+ #---------Split off the numerator/denominator
+ my @factors = split(q[/], $ans);
+ my $num = Formula($factors[0]);
+ my $den = ($#factors > 0) ? Formula($factors[1]) : Formula("1");
+
+ my $old_evaluator = fun_cmp($ans, var => @vars);
+
+ my $new_evaluator = sub {
+ my $student_ans = shift;
+ my $student_ans_text = $student_ans;
+
+ #---------For string answers----------------------------------------------
+ if ($student_ans_text =~ /not/) {
+ my $old_eval_string = str_cmp($ans_text);
+ my $ans_hash = $old_eval_string->evaluate($student_ans_text);
+ $ans_hash->{correct_ans} = $ans_text;
+ $ans_hash->{student_ans} = $student_ans_text;
+ $ans_hash->{original_student_ans} = $student_ans_text;
+ return $ans_hash;
+ }
+ #-------------------------------------------------------------------------
+
+ my $ans_hash = $old_evaluator->evaluate($student_ans);
+
+ if ($ans_hash->{score} == 1) {
+ #--------Check for a scalar answer in case and the student entered a decimal
+ if ($student_ans !~ /[a-zA-Z]/ && $ans !~ /[a-zA-Z]/) {
+ $ans_hash->{correct_ans} = $ans_text;
+ $ans_hash->{student_ans} = $student_ans_text;
+ $ans_hash->{original_student_ans} = $ans_hash->{student_ans};
+ return $ans_hash;
+ }
+ #--------------------------------------------------------------------
+
+ #--------Split off the numerator/denominator-------------------------
+ my $student_ans_mo = Formula($student_ans_text);
+ my ($student_num, $student_den);
+ my @student_factors;
+
+ # use MO parse tree rather than regex split because of surrounding parens from mathquill
+ if ($student_ans_mo->{tree}->class eq 'BOP' && $student_ans_mo->{tree}{bop} =~ m!/!) {
+ $student_num = $student_ans_mo->{tree}{lop};
+ $student_den = $student_ans_mo->{tree}{rop};
+ @student_factors = ($student_num->string, $student_den->string);
+ } else {
+ $student_num = $student_ans_mo;
+ $student_den = Formula('1');
+ @student_factors = ($student_num->string);
+ }
+ #--------------------------------------------------------------------
+
+ if ($#factors != $#student_factors) {
+ $ans_hash->{score} = 0;
+ } else {
+ my $num_eval = fun_cmp($num, var => @vars);
+ my $num_hash = $num_eval->evaluate($student_num);
+ $ans_hash->{score} = $num_hash->{score};
+
+ if ($#factors > 0) {
+ my $den_eval = fun_cmp($den, var => @vars);
+ my $den_hash = $den_eval->evaluate($student_den);
+ $ans_hash->{score} = $den_hash->{score};
+ }
+ }
+
+ $ans_hash->{ans_message} = "Simplify your answer." if ($ans_hash->{score} == 0);
+ }
+
+ return $ans_hash;
+ };
+
+ Context()->strings->remove("Does not simplify" => ());
+ return $new_evaluator;
+}
+
+###################################################################
+# 9) Writes a fraction in reduced form.
+#
+
+sub ReduceFraction {
+ my $num = shift;
+ my $den = shift;
+ my $n = 1;
+ my $d = 1;
+
+ ($n, $d) = reduce($num, $den);
+ my $result = "$n/$d";
+ if ($d == 1) { $result = "$n"; }
+
+ return ($result);
+}
+
+###################################################################
+# 10) Checks a list of equations.
+# Designed for checking a list of asymptotes
+
+sub equation_cmp_list {
+
+ loadMacros("extraAnswerEvaluators.pl");
+
+ my $ans = shift;
+ my @vars = @_;
+
+ my $format_ans = $ans;
+ $format_ans =~ tr/ //; #Remove any whitespace
+ my @ans_list = split(/[,]/, $format_ans); #Split off the terms
+
+ #
+ # my $old_evaluator2 = str_cmp($ans_list[1]);
+ # Would like to change this to an equation evaluator later
+ # my $old_evaluator1 = equation_cmp($ans[0],vars=>@vars);
+ # my $old_evaluator2 = equation_cmp($ans[1],vars=>@vars);
+
+ my $new_evaluator = sub {
+
+ local ($eq_eval, $ans_hash);
+
+ my $student_ans = shift;
+ chomp $student_ans;
+ my $format_st = $student_ans;
+ $format_st =~ tr/ //; #Remove any whitespace
+
+ my @student_list = split(/,/, $format_st);
+
+ my $Correct = -1;
+ foreach $i (0 .. $#ans_list) #Count the number of matches between the lists
+ {
+ my $eq_eval = str_cmp($ans_list[$i]);
+ foreach $j (0 .. $#student_list) {
+ $ans_hash = $eq_eval->evaluate($student_list[$j]);
+ if ($ans_hash->{score} == 1) { $Correct = $Correct + 1; }
+ }
+ }
+ my $Duplicates = -1;
+ foreach $i (0 .. $#student_list) #Check for duplicates in the student's list
+ {
+ my $eq_eval = str_cmp($student_list[$i]);
+ foreach $j (0 .. $#student_list) {
+ $ans_hash = $eq_eval->evaluate($student_list[$j]);
+ if ($ans_hash->{score} == 1) { $Duplicates = $Duplicates + 1; }
+ }
+ }
+ if ($Correct != $#ans_list || $Duplicates != $#student_list) {
+ $ans_hash->{score} = 0;
+ if ($Duplicates != $#student_list) {
+ $ans_hash->setKeys('ans_message' => "You have listed an asymptote more than once.");
+ }
+ } else {
+ $ans_hash->{score} = 1;
+ }
+ $ans_hash->{correct_ans} = $ans;
+ $ans_hash->{student_ans} = $student_ans;
+ $ans_hash->{original_student_ans} = $ans_hash->{student_ans};
+ # $ans_hash->{type} = '',
+ $ans_hash->{preview_text_string} = $student_ans;
+ $ans_hash->{preview_latex_string} = $student_ans;
+ return $ans_hash;
+ };
+ return $new_evaluator;
+}
+
+1;
diff --git a/macros/deprecated/Dartmouthmacros.pl b/macros/deprecated/Dartmouthmacros.pl
new file mode 100755
index 0000000000..09678528b2
--- /dev/null
+++ b/macros/deprecated/Dartmouthmacros.pl
@@ -0,0 +1,304 @@
+#!/usr/bin/perl
+
+# this is equivalent to use strict, but can be used within the Safe compartment.
+BEGIN {
+ be_strict;
+}
+
+## Some local macros
+
+sub trs_mod {
+ my @inputs = @_;
+ my $a = $inputs[0];
+ my $b = $inputs[1];
+ my $zero = 1e-12;
+ if ($b < 0) { $b = -$b; }
+
+## Want mod(a,b) but perl and int are flawed
+ my $modvalue = $a;
+ # if ($a >= 0 && $a < $b) {$modvalue = $a;}
+
+ while ($modvalue >= $b) { $modvalue = $modvalue - $b; }
+ while ($modvalue < 0) { $modvalue = $modvalue + $b; }
+ if (abs($modvalue) <= $zero) { $modvalue = 0; }
+ if (abs($modvalue - $b) <= $zero) { $modvalue = 0; }
+## Fudge for roundoff error
+ return $modvalue;
+}
+
+## Compute the product of a scalar and a vector (scalar first)
+sub scalar_mult_vector {
+ ## Put the parameters passed into an array of values
+ my @vector = @_;
+
+ ## Split off the first entry as the scalar, and the rest as the vector
+ my $scalar = $vector[0];
+ my @vectorb = @vector[ (1 .. $#vector) ];
+
+ ## Initialize scalar multiple as empty vector
+ my @scalar_multiple = ();
+
+ my $i;
+ for ($i = 0; $i <= $#vectorb; $i++) {
+ $scalar_multiple[$i] = $scalar * $vectorb[$i];
+ }
+ return @scalar_multiple;
+}
+
+## Compute the sum of two vectors
+## Perl doesn't seem to have builtin array arithmetic
+sub vector_sum {
+ ## Put the parameters passed into an array of values
+ my @vector = @_;
+ ## $#vector is the number of elements in vector minus 1
+ my $halflength = ($#vector - 1) / 2;
+ ## Slice the input into two equal length vectors
+ my @vectora = @vector[ (0 .. $halflength) ];
+ my @vectorb = @vector[ ($halflength + 1 .. $#vector) ];
+
+ ## Initialize vector sum to empty array
+ my @vector_sum = ();
+
+ my $i;
+ for ($i = 0; $i <= $#vectora; $i++) {
+ $vector_sum[$i] = $vectora[$i] + $vectorb[$i];
+ }
+ return @vector_sum;
+}
+
+## Compute the difference of two vectors
+sub vector_diff {
+ ## Put the parameters passed into an array of values
+ my @vector = @_;
+ ## $#vector is the number of elements in vector minus 1
+ my $halflength = ($#vector - 1) / 2;
+ ## Slice the input into two equal length vectors
+ my @vectora = @vector[ (0 .. $halflength) ];
+ my @vectorb = @vector[ ($halflength + 1 .. $#vector) ];
+
+ return vector_sum(@vectora, scalar_mult_vector(-1, @vectorb));
+}
+
+## Compute the length of a vector
+sub vec_length {
+ ## Put the paramaters passed into an array of values
+ my @vector = @_;
+
+ ## Initialize maximum value to first element
+ my $vector_length = 0;
+
+ my $i;
+ for ($i = 0; $i <= $#vector; $i++) {
+ $vector_length = $vector_length + $vector[$i] * $vector[$i];
+ }
+ $vector_length = sqrt($vector_length);
+ return $vector_length;
+}
+
+sub vector_length {
+ my @vector = @_;
+ return vec_length(@vector);
+}
+
+## Computes the dot product of two vectors (assumed of the same dimension)
+sub dot_product {
+ ## Put the parameters passed into an array of values
+ my @vector = @_;
+ ## $#vector is the number of elements in vector minus 1
+ my $halflength = ($#vector - 1) / 2;
+ ## Split the input into two equal length vectors
+ my @vectora = @vector[ (0 .. $halflength) ];
+ my @vectorb = @vector[ ($halflength + 1 .. $#vector) ];
+
+ ## Initialize dot product to zero
+ my $dot = 0;
+
+ my $i;
+ for ($i = 0; $i <= $#vectora; $i++) {
+ $dot = $dot + $vectora[$i] * $vectorb[$i];
+ }
+ return $dot;
+}
+
+sub cross_product {
+ ## Put the parameters passed into an array of values
+ my @vector = @_;
+
+ ## Slice the input into two equal length vectors
+ my @vectora = @vector[ (0 .. 2) ];
+ my @vectorb = @vector[ (3 .. 5) ];
+
+ ## Initialize dot product to zero
+ my @cross = ();
+
+ $cross[0] = $vectora[1] * $vectorb[2] - $vectora[2] * $vectorb[1];
+ $cross[1] = $vectora[2] * $vectorb[0] - $vectora[0] * $vectorb[2];
+ $cross[2] = $vectora[0] * $vectorb[1] - $vectora[1] * $vectorb[0];
+ return @cross;
+}
+
+## Compute the maximum value in a list
+#sub max {
+# ## Put the paramters passed into an array of values
+# my @values = @_;
+#
+# ## Initialize maximum value to first element
+# my $max = $values[0];
+#
+# my $i;
+# for ($i=1; $i <= $#values; $i++)
+# {
+# if ($values[$i] > $max) {
+# $max = $values[$i];
+# }
+# }
+# return $max;
+#}
+
+## Compute the minimum value in a list
+#sub min {
+# ## Put the paramters passed into an array of values
+# my @values = @_;
+#
+# ## Initialize minimum value to first element
+# my $min = $values[0];
+#
+# my $i;
+# for ($i=1; $i <= $#values; $i++)
+# {
+# if ($values[$i] < $min) {
+# $min = $values[$i];
+# }
+# }
+# return $min
+#}
+
+## clean_scalar_string is invoked to make expressions like "$a x" look
+## better when $a = 0, -1, 1
+## Usage: clean_scalar_string(scalar, "quoted string");
+## Example: clean_scalar_string(-1,"\pi") returns "-\pi"
+sub clean_scalar_string {
+ my $local_scalar = shift;
+ my $local_fixed_object = shift;
+ my $return_object;
+
+ if ($local_scalar == 0) { $return_object = "0"; }
+ elsif ($local_scalar == 1) { $return_object = "$local_fixed_object"; }
+ elsif ($local_scalar == -1) { $return_object = "-$local_fixed_object"; }
+ else { $return_object = "${local_scalar}${local_fixed_object}"; }
+ return $return_object;
+}
+
+## Computes the greatest common divisor of two integers
+## Example: gcd(-300, 125) returns 25
+sub trs_gcd {
+ my $a = shift;
+ my $b = shift;
+ my $c;
+ my $abs_a = abs($a);
+ my $abs_b = abs($b);
+ if ($abs_b == 0) { return $abs_a; }
+ else {
+ $c = $abs_a % $abs_b;
+ return trs_gcd($abs_b, $c);
+ }
+}
+
+## reduced_fraction takes a pair of integers $numerator, $denominator
+## ($denominator != 0) and returns an array of two elements @fraction
+## $fraction[0] is the reduced numerator; $fraction[1] the reduced
+## denominator
+## Puts the sign of the fraction, if negative, in the numerator
+##
+## Usage: @fraction = reduced_fraction($numerator, $denominator)
+##
+sub reduced_fraction {
+ my $local_numerator = shift;
+ my $local_denominator = shift;
+ my $sign_of_num = 1;
+ my $sign_of_denom = 1;
+ my $sign_of_quotient;
+ my @local_fraction = ();
+
+ if ($local_numerator < 0) { $sign_of_num = -1; }
+ if ($local_denominator < 0) { $sign_of_denom = -1; }
+ $sign_of_quotient = $sign_of_num * $sign_of_denom;
+
+ my $local_gcd = trs_gcd($local_numerator, $local_denominator);
+## reduced numerator
+ $local_fraction[0] = ($sign_of_quotient * abs($local_numerator)) / $local_gcd;
+## reduced denominator
+ $local_fraction[1] = abs($local_denominator) / $local_gcd;
+ return @local_fraction;
+}
+
+## Given Cartesian coordinates x and y returns an
+## array the zeroth element which is the radius
+## and the first element which is the argument
+## 0 <= theta < 2*pi
+##
+## Returns r=0 theta=0 for the origin
+##
+sub coordinates_polar {
+ my $x = shift;
+ my $y = shift;
+ my @polar = ();
+ my $radius = 0;
+ my $theta = 0;
+ my $pi = acos(-1);
+
+ $radius = sqrt($x**2 + $y**2);
+ if ($radius != 0) {
+ if ($x == 0) {
+ if ($y > 0) { $theta = $pi / 2; }
+ else { $theta = 3 * $pi / 2; }
+ } else {
+ if ($y > 0) {
+ if ($x > 0) { $theta = atan($y / $x); }
+ else { $theta = $pi - atan(-$y / $x); }
+ } else {
+ if ($x > 0) { $theta = 2 * $pi + atan($y / $x); }
+ else { $theta = $pi + atan($y / $x); }
+ }
+ }
+ }
+ @polar = ($radius, $theta);
+ return @polar;
+}
+
+## Cylindarical coordinates from polar routine
+sub coordinates_cylindrical {
+ my $x = shift;
+ my $y = shift;
+ my $z = shift;
+ my @cylindrical = (coordinates_polar($x, $y), $z);
+ return @cylindrical;
+}
+
+## Spherical Coordinates from polar routine
+##
+sub coordinates_spherical {
+ my $x = shift;
+ my $y = shift;
+ my $z = shift;
+ my $rho = 0;
+ my $phi = 0;
+ $rho = sqrt($x * $x + $y * $y + $z * $z);
+ my @npolar = ();
+ @npolar = coordinates_polar($x, $y);
+ my @spherical = ();
+
+ if ($x == 0 && $y == 0) {
+ if ($z >= 0) { $phi = 0; }
+ else { $phi = acos(-1); }
+ @spherical = ($rho, 0, $phi);
+ } else {
+ $rho = sqrt($x * $x + $y * $y + $z * $z);
+
+ @spherical = ($rho, $npolar[1], acos($z / $rho));
+ }
+ return @spherical;
+}
+
+1;
+
diff --git a/macros/deprecated/MUHelp.pl b/macros/deprecated/MUHelp.pl
new file mode 100644
index 0000000000..20d8faef9a
--- /dev/null
+++ b/macros/deprecated/MUHelp.pl
@@ -0,0 +1,49 @@
+sub BBRED {
+ MODES(TeX => '{\\color{red} ', HTML => '');
+}
+
+sub EBRED {
+ MODES(TeX => '}', HTML => ' ');
+}
+
+$main::BBRED = BBRED();
+$main::EBRED = EBRED();
+
+$badmessage = "$BBRED Something helpful should go here. Please inform your instructor that it is missing! $EBRED";
+
+sub MUHelp {
+ my $type = shift;
+ return $badmessage unless defined($type);
+
+ my $rString = "";
+ my $typeOkay = 0;
+
+ if ($type =~ m/sqrt/i) {
+ $rstring =
+ "If necessary, type ${BBOLD}sqrt()${EBOLD} to enter a square root. For example, to enter \\(\\sqrt{7x}\\), you would type your answer as sqrt(7x). You must put parentheses around everything you want to take the square root of!!";
+ $typeOkay = 1;
+ } elsif ($type =~ m/absvaleqn/i) {
+ $rstring =
+ "Enter your answers as a comma separated list if there is more than one correct answer. Type ${BBOLD}no solution${EBOLD} if the equation has no solution.";
+ $typeOkay = 1;
+ } elsif ($type =~ m/logans/i) {
+ $rstring =
+ "Give an exact answer. If necessary, type ${BBOLD}log(b,x)${EBOLD} to enter \\(\\log_b(x)\\) as an answer. For example, type ${BBOLD}log(3,5)${EBOLD} to enter \\(\\log_3(5)\\). You may also use ${BBOLD}ln(15)${EBOLD} for \\(\\ln(15)\\), and ${BBOLD}log10(20)${EBOLD} for \\(\\log(20)\\). Enter your answers as a comma separated list if there is more than one correct answer. Type ${BBOLD}no solution${EBOLD} if the equation has no solution.";
+ $typeOkay = 1;
+ } elsif ($type =~ m/lineareqns/i) {
+ $rstring =
+ "If necessary, type ${BBOLD}no solutions${EBOLD} if the equation has no solution or type ${BBOLD}infinitely many${EBOLD} if there are infinitely many solutions.";
+ $typeOkay = 1;
+ } elsif ($type =~ m/quadeqns/i) {
+ $rstring =
+ "Enter your answers as a comma separated list if there is more than one correct answer. Type ${BBOLD}no solution${EBOLD} if the equation has no solution. If necessary, type ${BBOLD}sqrt()${EBOLD} to enter a square root. For example, to enter \\(\\sqrt{7x}\\), you would type your answer as sqrt(7x). You must put parenthesis are everything you want to take the square root of!!";
+ $typeOkay = 1;
+ }
+
+ if ($typeOkay == 1) { return $rstring; }
+ return $badmessage;
+}
+
+1;
+
+## Append .helpLink("exponents","Click here for further help.") to a string to add a help link.
diff --git a/macros/deprecated/README.md b/macros/deprecated/README.md
new file mode 100644
index 0000000000..86e399f697
--- /dev/null
+++ b/macros/deprecated/README.md
@@ -0,0 +1,6 @@
+# macros/deprecated folder
+
+This folder contains macros intended to be deprecated. These are either macros that do not
+contain functionality that is general for other pg problems (generally are specific for a particular
+university) or are no longer necessary (the functionality is either in other pg macros or webwork
+itself).
diff --git a/macros/deprecated/freemanMacros.pl b/macros/deprecated/freemanMacros.pl
new file mode 100755
index 0000000000..86fcb54f35
--- /dev/null
+++ b/macros/deprecated/freemanMacros.pl
@@ -0,0 +1,541 @@
+sub textbook_ref {
+ my ($text, $sec, $ex) = @_;
+ return "";
+ return "$text section $sec, exercise $ex";
+}
+
+sub textbook_ref_corr {
+ return "";
+ return "Similar to " . textbook_ref(@_) . ".";
+}
+
+sub textbook_ref_exact {
+ return "";
+ return "From " . textbook_ref(@_) . ".";
+}
+
+sub list_random_multi_uniq($@) {
+ my ($n, @list) = @_;
+ my @result;
+ while (@result < $n) {
+ my $i = random(0, $#list, 1);
+ if ($i == 0) {
+ push @result, shift @list;
+ } elsif ($i == $#list) {
+ push @result, pop @list;
+ } else {
+ push @result, $list[$i];
+ $list[$i] = pop @list;
+ }
+ }
+ return @result;
+}
+
+sub IINT {
+ return '\int\!\!\int';
+}
+
+sub IIINT {
+ return '\int\!\!\int\!\!\int';
+}
+
+$IINT = IINT();
+$IIINT = IIINT();
+
+sub VUSAGE {
+ return
+ $BBOLD
+ . ' Usage: '
+ . $EBOLD
+ . 'To enter a vector, for example \(\langle x,y,z\rangle\), type '
+ . $LQ . '\('
+ . $LTS
+ . '\) x, y, z \('
+ . $GTS . '\)'
+ . $RQ;
+}
+
+$VUSAGE = VUSAGE();
+
+sub PUSAGE {
+ return $BBOLD . ' Usage: ' . $EBOLD . 'To enter a point, for example \( (x,y,z) \), type "(x, y, z)".';
+}
+
+$PUSAGE = PUSAGE();
+
+#sam# copied from bkellMacros.pl -- no reason to maintain two macro files.
+#sam# FIXME -- these should probably lose their "bkell_" prefixes.
+
+# bkellMacros.pl
+# Brian Kell
+# Last updated 3:24 CDT, 24 Jun 2007
+
+###############################################################################
+# bkell_linear_simplify($a, $b)
+#
+# Returns a string representing $a*x+$b in simplified form, where "simplified"
+# means something along the lines of the following:
+#
+# a b output
+# --- --- ------
+# 0 0 0
+# 1 1 x+1
+# -1 -1 -x-1
+# -1 5 5-x
+# 2 0 2x
+# -4 3 3-4x
+# -4 -3 -4x-3
+#
+sub bkell_linear_simplify {
+ my ($a, $b) = @_;
+
+ if ($a == 0) {
+ return "$b";
+ } elsif ($b == 0) {
+ if ($a == -1) {
+ return "-x";
+ } elsif ($a == 1) {
+ return "x";
+ } else {
+ return "${a}x";
+ }
+ } elsif ($a < 0 && $b > 0) {
+ if ($a == -1) {
+ return "$b-x";
+ } else {
+ return "$b-" . (abs $a) . "x";
+ }
+ } elsif ($a == 1) {
+ return "x" . sprintf("%+d", $b);
+ } elsif ($a == -1) {
+ return "-x" . sprintf("%+d", $b);
+ } else {
+ return "${a}x" . sprintf("%+d", $b);
+ }
+}
+
+###############################################################################
+# bkell_simplify_fraction($num, $denom)
+#
+# Simplifies the fraction $num/$denom; returns the list ($num, $denom).
+#
+sub bkell_simplify_fraction {
+ my ($num, $denom) = @_;
+
+ if ($num == 0) { return (0, 1); }
+
+ my $sign = +1;
+ if ($num < 0) { $sign *= -1; $num *= -1; }
+ if ($denom < 0) { $sign *= -1; $denom *= -1; }
+
+ for ($i = 2; $i <= $num && $i <= $denom; ++$i) {
+ while ($num % $i == 0 && $denom % $i == 0) {
+ $num /= $i;
+ $denom /= $i;
+ }
+ }
+
+ return ($sign * $num, $denom);
+}
+
+###############################################################################
+# bkell_simplify_fraction_string($num, $denom [, $flags])
+#
+# Returns a string like "$num/$denom" in simplified form. If the string $flags
+# contains "+", then a leading "+" is included if the fraction is non-negative.
+# If $flags contains "0", then a fraction with a value of 0 will cause the
+# empty string to be returned. If $flags contains "1", then a fraction with a
+# value of 1 or -1 will cause only the sign to be returned (or the empty
+# string, if the fraction is non-negative and $flags does not contain "+"). If
+# $flags contains "(", then parentheses will be placed around "$num/$denom"
+# (the sign, if it exists, will be outside the parentheses); if $flags contains
+# "[", then parentheses will be used only if a sign is used and the denominator
+# is other than 1. A flag of "(" overrides "]". If $flags contains "f", then
+# the string returned will be in the form "{$num \over $denom}" instead of the
+# normal slashed version.
+#
+sub bkell_simplify_fraction_string {
+ my ($num, $denom, $flags) = @_;
+ if (!defined $flags) { $flags = ""; }
+
+ ($num, $denom) = bkell_simplify_fraction($num, $denom);
+
+ my $sign = ($num >= 0 ? ($flags =~ /\+/ ? "+" : "") : "-");
+ $num = abs $num;
+
+ my ($pre, $post);
+ if ($flags =~ /\(/ || ($flags =~ /\[/ && $sign ne "" && $denom != 1)) {
+ ($pre, $post) = ("(", ")");
+ } else {
+ ($pre, $post) = ("", "");
+ }
+
+ if ($num == 0 && $flags =~ /0/) { return ""; }
+ if ($denom == 1) {
+ if ($num == 1 && $flags =~ /1/) { return "$sign"; }
+ return "$sign$pre$num$post";
+ }
+ if ($flags =~ /f/) { return "$sign$pre {$num \\over $denom}$post"; }
+ return "$sign$pre$num/$denom$post";
+}
+
+###############################################################################
+# bkell_poly_term($coeff_num, $coeff_denom, $var [, "+"])
+#
+# Produces and simplifies a term of a polynomial of the general form
+# "($coeff_num/$coeff_denom)$var". Handles special cases (such as $num==0,
+# $denom==1, etc.). If the fourth argument is "+", then a leading "+" is
+# included if the term is positive.
+#
+sub bkell_poly_term {
+ my ($coeff_num, $coeff_denom, $var, $plus) = @_;
+ if (!defined $plus) { $plus = ""; }
+
+ if ($coeff_num == 0) { return ""; }
+
+ my $sign = +1;
+ if ($coeff_num < 0) { $sign *= -1; $coeff_num *= -1; }
+ if ($coeff_denom < 0) { $sign *= -1; $coeff_denom *= -1; }
+ $sign = ($sign > 0 ? ($plus eq "+" ? "+" : "") : "-");
+
+ my ($num, $denom) = bkell_simplify_fraction($coeff_num, $coeff_denom);
+
+ if ($denom == 1) {
+ if ($num == 1) {
+ return "$sign$var";
+ } else {
+ return "$sign$num$var";
+ }
+ }
+
+ return "$sign($num/$denom)$var";
+}
+
+###############################################################################
+# bkell_gcd($x, $y)
+#
+# Returns the greatest common divisor of $x and $y.
+#
+sub bkell_gcd {
+ my ($x, $y) = @_;
+ $x = abs $x;
+ $y = abs $y;
+ if ($x > $y) { ($x, $y) = ($y, $x); }
+ if ($x == 0) { return $y; }
+ my $r = $y % $x;
+ if ($r == 0) { return $x; }
+ return bkell_gcd($x, $r);
+}
+
+###############################################################################
+# bkell_poly_eval($x, $a_n, ..., $a_0)
+#
+# Evaluates the polynomial a_n*x^n + ... + a_1*x + a_0 at the given value of x.
+#
+sub bkell_poly_eval {
+ my $x = shift;
+ my $value = shift;
+ while (@_) { $value *= $x; $value += shift; }
+ return $value;
+}
+
+###############################################################################
+# bkell_real_zeros_finder($a_n, ..., $a_0)
+#
+# Returns a list of numerical approximations of the zeros of the polynomial
+# a_n*x^n + ... + a_1*x + a_0, in order from least to greatest.
+#
+# The possibility of overflow or underflow is ignored. Overflow is likely to be
+# a bigger problem than underflow.
+#
+# Do not use this code to guide missiles or control nuclear power plants.
+#
+sub bkell_real_zeros_finder {
+ my @coeffs = @_;
+
+ while (@coeffs && $coeffs[0] == 0) { shift @coeffs; }
+ my $deg = $#coeffs;
+
+ if ($deg == -1) {
+ return ("x"); # zero polynomial is zero everywhere
+ } elsif ($deg == 0) {
+ return (); # constant nonzero polynomial has no zeros
+ } elsif ($deg == 1) {
+ return (-$coeffs[1] / $coeffs[0]); # linear polynomial has one zero
+ }
+
+ # find critical points
+ my @derivative = @coeffs;
+ pop @derivative;
+ for (my $i = 0; $i < $#derivative; ++$i) {
+ $derivative[$i] *= @derivative - $i;
+ }
+ my @cp = bkell_real_zeros_finder(@derivative);
+
+ # if no critical points, we have a monotone function
+ if (!@cp) {
+ my ($lb, $rb) = (-1, 1);
+ my $y1 = bkell_poly_eval($lb, @coeffs);
+ my $y2 = bkell_poly_eval($rb, @coeffs);
+ my $sign = ($y1 < $y2 ? +1 : -1);
+ while ($sign * $y1 > 0) {
+ $lb *= 2;
+ $y1 = bkell_poly_eval($lb, @coeffs);
+ }
+ while ($sign * $y2 < 0) {
+ $rb *= 2;
+ $y2 = bkell_poly_eval($rb, @coeffs);
+ }
+ my $guess_x = ($lb + $rb) / 2;
+ while ($lb < $guess_x
+ && $guess_x < $rb
+ && (my $guess_y = bkell_poly_eval($guess_x, @coeffs)) != 0)
+ {
+ if (($y1 < 0 && $guess_y < 0) || ($y1 > 0 && $guess_y > 0)) {
+ $lb = $guess_x;
+ } else {
+ $rb = $guess_x;
+ }
+ $guess_x = ($lb + $rb) / 2;
+ }
+ return ($guess_x);
+ }
+
+ my @zeros = ();
+
+ # search for a zero to the left of the first critical point
+ {
+ my $y = bkell_poly_eval($cp[0], @coeffs);
+ # we catch this case when we check between critical points:
+ last if $y == 0;
+ # not really the limit, but only the sign matters:
+ my $lim = $coeffs[0] * ($deg % 2 ? -1 : +1);
+ if (($y > 0 && $lim < 0) || ($y < 0 && $lim > 0)) {
+ my ($lb, $rb) = (undef, $cp[0]);
+ my $guess_x = $rb - 10;
+ if ($guess_x >= 0) { $guess_x = -10; }
+ while ((!defined $lb || $lb < $guess_x)
+ && $guess_x < $rb
+ && (my $guess_y = bkell_poly_eval($guess_x, @coeffs)) != 0)
+ {
+ if (($y > 0 && $guess_y > 0) || ($y < 0 && $guess_y < 0)) {
+ $rb = $guess_x;
+ if (defined $lb) {
+ $guess_x = ($lb + $guess_x) / 2;
+ } else {
+ $guess_x *= 2;
+ }
+ } else {
+ $lb = $guess_x;
+ $guess_x = ($guess_x + $rb) / 2;
+ }
+ }
+ push @zeros, $guess_x;
+ }
+ }
+
+ # search for zeros between critical points
+ for (my $i = 0; $i < $#cp; ++$i) {
+ my $y1 = bkell_poly_eval($cp[$i], @coeffs);
+ if ($y1 == 0) {
+ push @zeros, $cp[$i];
+ next;
+ }
+ my $y2 = bkell_poly_eval($cp[ $i + 1 ], @coeffs);
+ if ($y2 == 0) {
+ push @zeros, $cp[ $i + 1 ];
+ ++$i;
+ next;
+ }
+ next if ($y1 > 0 && $y2 > 0) || ($y1 < 0 && $y2 < 0);
+ my ($lb, $rb) = ($cp[$i], $cp[ $i + 1 ]);
+ my $guess_x = ($lb + $rb) / 2;
+ while ($lb < $guess_x
+ && $guess_x < $rb
+ && (my $guess_y = bkell_poly_eval($guess_x, @coeffs)) != 0)
+ {
+ if (($y1 > 0 && $guess_y > 0) || ($y1 < 0 && $guess_y < 0)) {
+ $lb = $guess_x;
+ } else {
+ $rb = $guess_x;
+ }
+ $guess_x = ($lb + $rb) / 2;
+ }
+ push @zeros, $guess_x unless @zeros && $zeros[-1] == $guess_x;
+ }
+
+ # search for a zero to the right of the last critical point
+ {
+ my $y = bkell_poly_eval($cp[-1], @coeffs);
+ if ($y == 0 && $zeros[-1] != $cp[-1]) {
+ push @zeros, $cp[-1];
+ last;
+ }
+ if (($y > 0 && $coeffs[0] < 0) || ($y < 0 && $coeffs[0] > 0)) {
+ my ($lb, $rb) = ($cp[-1], undef);
+ my $guess_x = $lb + 10;
+ if ($guess_x <= 0) { $guess_x = 10; }
+ while ($lb < $guess_x
+ && (!defined $rb || $guess_x < $rb)
+ && (my $guess_y = bkell_poly_eval($guess_x, @coeffs)) != 0)
+ {
+ if (($y > 0 && $guess_y > 0) || ($y < 0 && $guess_y < 0)) {
+ $lb = $guess_x;
+ if (defined $rb) {
+ $guess_x = ($guess_x + $rb) / 2;
+ } else {
+ $guess_x *= 2;
+ }
+ } else {
+ $rb = $guess_x;
+ $guess_x = ($lb + $guess_x) / 2;
+ }
+ }
+ push @zeros, $guess_x unless @zeros && $zeros[-1] == $guess_x;
+ }
+ }
+
+ return @zeros;
+}
+
+###############################################################################
+# bkell_floor($x)
+#
+# Returns the floor of $x. Normally this would be done with POSIX::floor, but
+# WeBWorK doesn't allow you to use standard modules like POSIX.
+#
+sub bkell_floor {
+ my $x = shift;
+ my $floor = int $x;
+ if ($x < 0 && $x != $floor) { $floor -= 1; }
+ return $floor;
+}
+
+###############################################################################
+# bkell_ceil($x)
+#
+# Returns the ceiling of $x.
+#
+sub bkell_ceil {
+ return -bkell_floor(-shift);
+}
+
+###############################################################################
+# bkell_sigfigs($x, $n)
+#
+# Returns a string containing $x rounded to $n significant figures.
+#
+sub bkell_sigfigs {
+ my ($x, $n) = @_;
+
+ if ($x == 0) { return "0" . ($n > 1 ? "." : "") . ("0" x ($n - 1)); }
+
+ my $minus = "";
+ if ($x < 0) {
+ $minus = "-";
+ $x = -$x;
+ }
+
+ my $floor_log = bkell_floor(log($x) / log(10));
+
+ if ($floor_log + 1 >= $n) {
+ my $sf = 10**($floor_log - $n + 1);
+ return $minus . (sprintf("%.0f", $x / $sf) * $sf);
+ } else {
+ my $digits = $n - $floor_log - 1;
+ return $minus . sprintf("%.${digits}f", $x);
+ }
+}
+
+###############################################################################
+# bkell_125($x)
+#
+# Returns the value logarithmically nearest $x in the sequence
+# ..., -1000, -500, -200, -100, -50, -20, -10, -5, -2, -1, -0.5, -0.2,
+# -0.1, -0.05, -0.02, -0.01, ..., 0, ..., 0.01, 0.02, 0.05, 0.1,
+# 0.2, 0.5, 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, ... .
+#
+sub bkell_125 {
+ my $x = shift;
+
+ if ($x == 0) { return 0; }
+
+ my $sign = +1;
+ if ($x < 0) { $sign = -1; $x = -$x; }
+
+ my $log = log($x) / log(10);
+ my $characteristic = bkell_floor($log);
+ my $mantissa = $log - $characteristic;
+
+ my $log2 = log(2) / log(10);
+ my $log5 = log(5) / log(10);
+ my $m;
+ if ($mantissa < $log2 / 2) {
+ $m = 0;
+ } elsif ($mantissa < ($log2 + $log5) / 2) {
+ $m = $log2;
+ } elsif ($mantissa < ($log5 + 1) / 2) {
+ $m = $log5;
+ } else {
+ $m = 1;
+ }
+
+ return $sign * (10**($characteristic + $m));
+}
+
+###############################################################################
+# bkell_list_random_selection($n, @list)
+#
+# Returns a selection of $n distinct elements of @list. This is like
+# list_random_multi_uniq in freemanMacros.pl, except that this function will
+# always return the elements in the same order as they appear in @list.
+#
+sub bkell_list_random_selection {
+ my $n = int abs shift; # so strange $n won't cause infinite loop
+ my @list = @_;
+
+ my $needed = $n;
+ my @result = ();
+ while ($needed && @list) {
+ if (random(1, scalar @list) <= $needed) {
+ push @result, shift @list;
+ --$needed;
+ } else {
+ shift @list;
+ }
+ }
+
+ return @result;
+}
+
+###############################################################################
+# bkell_graph_axis($a, $b)
+#
+# Returns a list ($min, $max, $step), where $min <= $a, $max >= $b, $step is a
+# power of 10, $min and $max are multiples of $step, abs($min) <= 9*$step, and
+# abs($max) <= 9*$step. Useful for deciding bounds for the axis of a graph. For
+# example, to make a graph axis that can handle values between $a and $b, call
+# bkell_graph_axis, and then set the minimum value of the axis to $min and the
+# maximum to $max, and put tick marks every $step units.
+#
+sub bkell_graph_axis {
+ my ($a, $b) = @_;
+
+ if ($a > $b) { ($a, $b) = ($b, $a); }
+
+ my $s1 = 0;
+ my $s2 = 0;
+
+ if ($a != 0) { $s1 = bkell_floor(log(abs $a) / log(10)); }
+ if ($b != 0) { $s2 = bkell_floor(log(abs $b) / log(10)); }
+
+ my $s = ($s1 > $s2 ? $s1 : $s2);
+
+ $step = 10**$s;
+ $min = $step * bkell_floor($a / $step);
+ $max = $step * bkell_ceil($b / $step);
+
+ return ($min, $max, $step);
+}
+
+####################################################################### EOF ###
diff --git a/macros/deprecated/hhAdditionalMacros.pl b/macros/deprecated/hhAdditionalMacros.pl
new file mode 100755
index 0000000000..6f430ae29b
--- /dev/null
+++ b/macros/deprecated/hhAdditionalMacros.pl
@@ -0,0 +1,437 @@
+# additional macros developed in conjunction with the problems from
+# the consortium (hughes-hallett) calculus text
+#
+# written by Gavin LaRose,
+# version 1.2
+# 1.2: added reduced_frac()
+# 1.1: added numorfun_cmp()
+#
+# shufflemap
+# produces a reference to a hash of indices from 0 to n-1 pointing to
+# a random permutation of the same, and the index that points to 0
+#
+# string_list_cmp
+# check a comma-separated list of strings as student answers and
+# check them with the standard str_cmp evaluator. this is really
+# John Jones' number_list_cmp rewritten to be do string comparison
+#
+# integrand_fun_cmp
+# an answer evaluator that allows checking functions with 'dt' in them;
+# redundant now that the Parser allows checking with multicharacter
+# variables
+#
+# numfun_cmp
+# an answer evaluator that requires that the answer be
+# simplified to the number given without returning an error if
+# it's a function
+#
+# numorfun_cmp
+# an answer evaluator that first checks to see if the student answer
+# checks correctly as a number, and if so returns that result; if not,
+# repeats the check with a function checker and returns that. options
+# for the respective checkers are provided as separate hash references
+#
+# classify_cmp
+# take a correct answer of the form "val=string, val=string.."
+# and check a comma-separated list of student answers against these
+# if the option 'order'=>'strict' is provided, requires that the
+# answers be in the same order. any other options are passed to
+# num_cmp to check the val entries; strings are checked with a
+# standard str_cmp
+# doesn't currently allow for passing in of a reference to an array
+# of solutions to generate a list of evaluators, but that would be
+# easy to add
+#
+
+sub reduced_frac {
+ my ($num, $den) = @_;
+
+ if ($num / $den == int($num / $den)) {
+ return $num / $den;
+ } else {
+ for (my $j = ($num > $den ? $den : $num); $j > 1; $j--) {
+ if ($num / $j == int($num / $j) && $den / $j == int($den / $j)) {
+ $num = $num / $j;
+ $den = $den / $j;
+ }
+ }
+ if ($den == 1) {
+ return $num;
+ } else {
+ return ("{$num" . '\over' . "$den}");
+ }
+ }
+}
+
+sub shufflemap {
+ my $n = shift();
+ my $m = $n - 1;
+ my $zeroind;
+
+ my %map = ();
+
+ my @vals = (0 .. $m);
+ for (my $i = 0; $i < $m; $i++) {
+ my $j = random(0, $m - $i, 1);
+ $map{$i} = $vals[$j];
+ $zeroind = $i if ($map{$i} == 0);
+ splice(@vals, $j, 1);
+ }
+ $map{$m} = $vals[0];
+ $zeroind = $m if ($map{$m} == 0);
+
+ return ({%map}, $zeroind);
+}
+
+sub string_list_cmp {
+ my $cList = shift();
+ my %opts = @_;
+
+ my $ordered = 0;
+ if (defined($opts{'ordered'})) {
+ $ordered = 1 if ($opts{'ordered'});
+ delete($opts{'ordered'});
+ }
+
+ my @cAns = split(/,\s*/, $cList);
+
+ my @ansEval = ();
+ foreach (@cAns) {
+ push(@ansEval, str_cmp($_, %opts));
+ }
+
+ return (sub {
+ my $sList = shift();
+ my @sAns = split(/,\s*/, $sList);
+
+ my $res = new AnswerHash;
+
+ if ($ordered) {
+ if (@sAns) {
+ my $res = $ansEval[0]->evaluate($sAns[0]);
+ for (my $i = 1; $i < @sAns && $i < @ansEval; $i++) {
+ $res = $res->AND($ansEval[$i]->evaluate($sAns[$i]));
+ }
+ if (@ansEval > @sAns) {
+ $res->{'score'} = 0;
+ for (my $j = @sAns; $j < @ansEval; $j++) {
+ my $fakeRes = $ansEval[$j]->evaluate('something');
+ $res->{'correct_ans'} .= "AND " . $fakeRes->{'correct_ans'};
+ }
+ } elsif (@sAns > @ansEval) {
+ $res->{'score'} = 0;
+ for (my $j = @ansEval; $j < @sAns; $j++) {
+ my $fakeRes = $ansEval[0]->evaluate($sAns[$j]);
+ $res->{'student_ans'} .= ", " . $fakeRes->{'student_ans'};
+ $res->{'original_student_ans'} .= ", " . $fakeRes->{'original_student_ans'};
+ }
+ }
+ return $res;
+ } else {
+ my $res = $ansEval[0]->evaluate('');
+ for (my $i = 1; $i < @ansEval; $i++) {
+ $res = $res->AND($ansEval[$i]->evaluate(''));
+ }
+ return $res;
+ }
+ } else {
+ # go through and check each in turn
+ my $res;
+ my @missedAnsEval = ();
+ my @doneStuAns = ();
+ my $stuAnsDone = '';
+ for (my $i = 0; $i < @ansEval; $i++) {
+ my $doneOne = 0;
+ for (my $j = 0; $j < @sAns; $j++) {
+ next if ($stuAnsDone =~ /\b$j\b/);
+ my $curRes = $ansEval[$i]->evaluate($sAns[$j]);
+ if ($curRes->{'score'}) {
+ if (ref($res)) {
+ $res = $res->AND($curRes);
+ $res->{'student_ans'} .= ", " . $curRes->{'student_ans'};
+ $res->{'original_student_ans'} .= ", " . $curRes->{'original_student_ans'};
+ } else {
+ $res = $curRes;
+ }
+ $stuAnsDone .= "$j,";
+ $doneOne = 1;
+ push(@doneStuAns, $j);
+ last;
+ }
+ }
+ push(@missedAnsEval, $i) if (!$doneOne);
+ }
+ # did we miss any correct answers?
+ if (@missedAnsEval) {
+ $res->{'score'} = 0;
+
+ foreach (@missedAnsEval) {
+ my $fakeRes = $ansEval[$_]->evaluate('something');
+ $res->{'correct_ans'} .= " AND " . $fakeRes->{'correct_ans'};
+ }
+ }
+ # did we miss any student answers?
+ if (@doneStuAns != @sAns) {
+ $res->{'score'} = 0;
+
+ for (my $i = 0; $i < @sAns; $i++) {
+ if ($stuAnsDone !~ /\b$i\b/) {
+ $res->{'student_ans'} .= ", " . uc($sAns[$i]);
+ $res->{'original_student_ans'} .= ", " . $sAns[$i];
+ $res->{'preview_latex_string'} .= "\\quad \\mbox{" . uc($sAns[$i]) . "}";
+ }
+ }
+ }
+ return $res;
+ }
+ });
+}
+
+# integrand_fun_cmp
+# an answer evaluator to allow checking functions with 'dt' in them
+#
+sub integrand_fun_cmp {
+ my $cAns = shift();
+ my %opts = @_;
+
+ if (defined($opts{'var'}) && $opts{'var'}) {
+ $differential = "d" . $opts{'var'}->[0]; # we assume the first
+ push(@{ $opts{'var'} }, 'Q'); # variable is the
+ } else { # independent variable
+ $differential = "dx";
+ $opts{'var'} = [ 'x', 'Q' ];
+ }
+ $cAns =~ s/$differential/Q/;
+ $eval = fun_cmp($cAns, %opts);
+
+ return (sub {
+ my $sAns = shift();
+ $sAns = '' if (!defined($sAns));
+ $sAns =~ s/$differential/Q/;
+
+ my $ansHash = $eval->evaluate($sAns);
+ # replace Q with the correct differential
+ # are there missing values?
+ foreach my $v (qw( correct_ans original_correct_ans student_ans
+ original_student_ans preview_text_string
+ preview_latex_string ))
+ {
+ $ansHash->{$v} = '' if (!defined($ansHash->{$v}));
+ }
+ $ansHash->{'correct_ans'} =~ s/Q/$differential/;
+ $ansHash->{'original_correct_ans'} =~ s/Q/$differential/;
+ $ansHash->{'student_ans'} =~ s/Q/$differential/;
+ $ansHash->{'original_student_ans'} =~ s/Q/$differential/;
+ $ansHash->{'preview_text_string'} =~ s/Q/$differential/;
+ $ansHash->{'preview_latex_string'} =~ s/Q/$differential/;
+
+ return $ansHash;
+ });
+}
+
+# numfun_cmp
+# an answer evaluator that requires that the answer be
+# simplified to the number given without returning an error if
+# it's a function
+#
+sub numfun_cmp {
+ my $cAns = shift();
+ my %opts = @_;
+
+ my $eval1 = fun_cmp($cAns, @_);
+ my $eval2 = num_cmp($cAns, @_);
+
+ return (sub {
+ my $sAns = shift();
+ my $ans1 = $eval1->evaluate($sAns);
+ my $ans2 = $eval2->evaluate($sAns);
+
+ if ($ans2->{'score'}) {
+ return ($ans2);
+ } else {
+ $ans1->{'score'} = 0;
+ return ($ans1);
+ }
+ });
+}
+
+# numorfun_cmp
+# an answer evaluator that first checks to see if the student answer
+# checks correctly as a number, and if so returns that result; if not,
+# repeats the check with a function checker and returns that.
+#
+sub numorfun_cmp {
+ my $cAns = shift();
+ my ($numOptsRef, $funOptsRef) = @_;
+ my $eval1 = fun_cmp($cAns, %$funOptsRef);
+ my $eval2 = num_cmp($cAns, %$numOptsRef);
+
+ return (sub {
+ my $sAns = shift();
+ my $ans1 = $eval1->evaluate($sAns);
+ my $ans2 = $eval2->evaluate($sAns);
+
+ if ($ans2->{'score'}) {
+ return ($ans2);
+ } else {
+ return ($ans1);
+ }
+ });
+}
+
+sub classify_num_cmp {
+ return classify_cmp(@_, 'classifymode' => 'num');
+}
+
+sub classify_fun_cmp {
+ return classify_cmp(@_, 'classifymode' => 'fun');
+}
+
+sub classify_cmp {
+ my $cAns = shift();
+ my %opts = @_;
+
+ my $classifymode = 'num';
+ if (defined($opts{'classifymode'})) {
+ $classifymode = $opts{'classifymode'};
+ delete($opts{'classifymode'});
+ }
+
+ my $ordered = 0;
+ if (defined($opts{'order'})) {
+ $ordered = 1 if ($opts{'order'} eq 'strict');
+ delete($opts{'order'});
+ }
+
+ my @pts = split(/\s*,\s*/, $cAns);
+
+ my @xeval;
+ my @ceval;
+ foreach (@pts) {
+ my ($x, $c) = split(/\s*=\s*/);
+ $c = '' if (!defined($c));
+ if ($classifymode eq 'num') {
+ push(@xeval, num_cmp($x, %opts));
+ } else {
+ push(@xeval, fun_cmp($x, %opts));
+ }
+ push(@ceval, str_cmp($c));
+ }
+
+ return (sub {
+ my $sAns = shift();
+ my @sAnsList = split(/\s*,\s*/, $sAns);
+ my @sAnsw = ();
+ foreach (@sAnsList) {
+ my ($x, $c) = split(/\s*=\s*/);
+ $x = 'none' if (!defined($x));
+ $c = '' if (!defined($c));
+ push(@sAnsw, [ $x, $c ]);
+ }
+
+ # now build a set of answer hashes, one for each student answer
+ my @results = ();
+ my @undone = (); # list of unused evaluators
+ my $nummatched = 0; # number of student answers matched
+ for (my $j = 0; $j < @xeval; $j++) {
+ my $done = 0;
+ if ($ordered) {
+ if ($j < @sAnsw) {
+ my $x_hash = $xeval[$j]->evaluate($sAnsw[$j]->[0]);
+ my $c_hash = $xeval[$j]->evaluate($sAnsw[$j]->[1]);
+ my $res = $x_hash->AND($c_hash);
+ $res->{'preview_text_string'} =
+ $x_hash->{'preview_text_string'} . "=" . $c_hash->{'preview_text_string'};
+ $res->{'preview_latex_string'} =
+ $x_hash->{'preview_latex_string'} . "=" . $c_hash->{'preview_latex_string'};
+ $results[$j] = $res;
+ $nummatched++;
+ } else {
+ last;
+ }
+ } else {
+ for (my $i = 0; $i < @sAnsw; $i++) {
+ my $x_hash = $xeval[$j]->evaluate($sAnsw[$i]->[0]);
+
+ if ($x_hash->score()) {
+ my $c_hash = $ceval[$j]->evaluate($sAnsw[$i]->[1]);
+
+ my $res = $x_hash->AND($c_hash);
+ $res->{'preview_text_string'} = $x_hash->{'preview_text_string'};
+ $res->{'preview_latex_string'} = $x_hash->{'preview_latex_string'};
+
+ if (defined($c_hash->{preview_text_string})
+ && $c_hash->{preview_text_string} ne '')
+ {
+ $res->{preview_text_string} .= "=" . $c_hash->{'preview_text_string'};
+ $res->{preview_latex_string} .= "=" . $c_hash->{'preview_latex_string'};
+ }
+ $results[$i] = $res;
+ $done = 1;
+ $nummatched++;
+ }
+ last if ($done);
+ }
+ if (!$done) {
+ push(@undone, [ $xeval[$j], $ceval[$j] ]);
+ }
+ }
+ last if ($nummatched == @sAnsw);
+ }
+ # check any student answers we haven't yet matched
+ if ($nummatched < @sAnsw) {
+ for (my $i = 0; $i < @sAnsw; $i++) {
+ next if (ref($results[$i]));
+ my ($x_hash, $c_hash, $res);
+ if (@undone) {
+ my $aeval = shift(@undone);
+ $x_hash = $aeval->[0]->evaluate($sAnsw[$i]->[0]);
+ $c_hash = $aeval->[1]->evaluate($sAnsw[$i]->[1]);
+ $res = $x_hash->AND($c_hash);
+ } else {
+ $res = new AnswerHash;
+ $res->{'correct_ans'} = 'none';
+ $res->{'original_correct_ans'} = 'none';
+ $res->{'student_ans'} = $sAnsw[$i]->[0] . "=" . $sAnsw[$i]->[1];
+ $res->{'original_student_ans'} = $sAnsw[$i]->[0] . "=" . $sAnsw[$i]->[1];
+ $res->{'score'} = 0;
+ $x_hash = $xeval[0]->evaluate($sAnsw[$i]->[0]);
+ $c_hash = $ceval[0]->evaluate($sAnsw[$i]->[1]);
+ }
+ $res->{'preview_text_string'} = $x_hash->{'preview_text_string'};
+ $res->{'preview_latex_string'} = $x_hash->{'preview_latex_string'};
+ if (defined($c_hash->{preview_text_string})
+ && $c_hash->{preview_text_string} ne '')
+ {
+ $res->{preview_text_string} .= "=" . $c_hash->{'preview_text_string'};
+ $res->{preview_latex_string} .= "=" . $c_hash->{'preview_latex_string'};
+ }
+ $results[$i] = $res;
+ }
+ }
+ # and get any other results they didn't submit
+ my @neededResults = ();
+ foreach (@undone) {
+ my $r = $_->[0]->evaluate('');
+ $r->AND($_->[1]->evaluate(''));
+ $r->{score} = 0;
+ push(@neededResults, $r);
+ }
+
+ # ok, now build a big and...
+ my $endres = (defined($results[0])) ? $results[0] : new AnswerHash;
+ for (my $i = 1; $i < @results; $i++) {
+ $endres = $endres->AND($results[$i]);
+ }
+ # for ( my $i=0; $i<@neededResults; $i++ ) {
+ # $endres = $endres->AND( $neededResults[$i] );
+ # }
+ $endres->{'student_ans'} = $sAns;
+ $endres->{'score'} = 0 if (@xeval > @sAnsw);
+
+ return $endres;
+ });
+}
+
+1;
+
diff --git a/macros/deprecated/littleneck.pl b/macros/deprecated/littleneck.pl
new file mode 100644
index 0000000000..bbfb1fa03f
--- /dev/null
+++ b/macros/deprecated/littleneck.pl
@@ -0,0 +1,191 @@
+#*****************************
+# Question mode variables
+#*****************************
+$PRACTICE_MODE = 0;
+loadMacros("problemRandomize.pl");
+#********************************************
+# Set up a "generate new problem" button
+#********************************************
+sub rand_button {
+ if ($PRACTICE_MODE == 1) {
+ ProblemRandomize(when => "Always", onlyAfterDue => 0, style => "Input");
+ } elsif ($PRACTICE_MODE == 2) {
+ ProblemRandomize(when => "Always", onlyAfterDue => 0, label => "Generate New Problem");
+ }
+}
+#**********************************************************
+# Subroutine to reduce fractions
+# Input : Num, Denom
+# Output: Num, Denom, Wholenum (if != 0, = $num/$denom)
+#
+# 22Jul09 - Returned Wholenum < 0 if num or denom < 0
+#**********************************************************
+sub reduce_fraction {
+ $n = $_[0];
+ $d = $_[1];
+ $wholenum = 0;
+ #
+ # **********************
+ # Remove negatives
+ # **********************
+ if ($n < 0) {
+ $num = -$n;
+ } else {
+ $num = $n;
+ }
+ if ($d < 0) {
+ $denom = -$d;
+ } else {
+ $denom = $d;
+ }
+ # ******************************
+ # Check for a whole number
+ # ******************************
+ if ($num / $denom == int($num / $denom)) {
+ $wholenum = $n / $d;
+ }
+ # ***************
+ # Find gcf
+ # ***************
+ else {
+ if ($num > $denom) {
+ $gcf = $denom;
+ } else {
+ $gcf = $num;
+ }
+ $found = 0;
+ while (($found == 0) && ($gcf > 1)) {
+ if (($num / $gcf == int($num / $gcf)) && ($denom / $gcf == int($denom / $gcf))) {
+ $found = 1;
+ } else {
+ $gcf--;
+ }
+ }
+ # ********************************
+ # If a GCF was found, reduce
+ # ********************************
+ if ($found == 1) {
+ $num = $num / $gcf;
+ $denom = $denom / $gcf;
+ }
+ }
+ # ***********************
+ # Restore negatives
+ # ***********************
+ if ($n < 0) {
+ $num = -1 * $num;
+ }
+ if ($d < 0) {
+ $denom = -1 * $denom;
+ }
+ return ($num, $denom, $wholenum);
+}
+#
+#**********************************************************
+# Subroutine to produce reduced display fraction
+# Input : Num($n), Denom($d)
+# Output: Num($n), Denom($d), Wholenum($w),Display($display)
+# calls reduce_fraction to get,
+# Output: Num($n), Denom($d), Wholenum($w),
+#(if Wholenum > 0,$display = $w
+# else $Display=\(\frac{$n}{$d}\)
+#**********************************************************
+sub display_fraction_long {
+ $n = $_[0];
+ $d = $_[1];
+ $w = 0;
+ $display = 0;
+ #call reduce_fraction
+ ($n, $d, $w) = reduce_fraction($n, $d, $w);
+ #Decide on display format
+ if ($w > 0) {
+ $display = $wholenumber;
+ } else {
+ $display = "\\frac{$num}{$denom}";
+ }
+ return ($n, $d, $w, $display);
+}
+#
+#
+#
+#
+#**********************************************************
+# Subroutine to produce reduced display fraction
+# Input : Num($n), Denom($d)
+# Output: Display($display)
+# calls reduce_fraction to get,
+# Output: Num($n), Denom($d), Wholenum($w),
+#(if Wholenum > 0,$display = $w
+# else $Display=\(\frac{$n}{$d}\)
+#**********************************************************
+sub display_fraction {
+ $n = $_[0];
+ $d = $_[1];
+ $w = 0;
+ $display = 0;
+ #call reduce_fraction
+ ($n, $d, $w) = reduce_fraction($n, $d, $w);
+ #Decide on display format
+ if ($w > 0) {
+ $display = $wholenumber;
+ } else {
+ $display = "\\frac{$num}{$denom}";
+ }
+ return ($display);
+}
+#*******************************************************************************
+# sqrt_simplify(Num,Natnum,Radnum,Texstr,Error)
+#
+# Description: Given a natural number, will find the greatest perfect square
+# that is a factor and use this to simplify the expression:
+# sqrt(number).
+# Input :
+# Num = Number whose sqrt is being taken
+#
+# Output:
+# Natnum => Natural number portion of solution (= 1 if no perfect square
+# divides evenly)
+# Radnum => The value still inside the radical
+# Texstr => A LaTex string of the form "Natnum \sqrt{$Radnum}" to be used
+# for displaying the solution.
+# Error => If == 1, then the number entered was bad (ie, negative, nonint)
+# (If necessary, this code can be made specific for the error)
+#*******************************************************************************
+sub sqrt_simplify {
+ $number = $_[0];
+ ($Natnum, $Radnum) = (1, 1);
+ $Error = 0;
+
+ #***********************************
+ # Check the number for validity
+ #***********************************
+ if (($number == int($number)) && ($number > 0)) {
+ $perfsqr = 1;
+ #*************************************************************************************
+ # Check all perfect squares up to the max and store the hightest one that divides
+ #*************************************************************************************
+ for ($i = 2; $i * $i <= $number; $i++) {
+ $sqr = $i * $i;
+ if (int($number / $sqr) == $number / $sqr) {
+ $perfsqr = $i;
+ }
+ }
+ $Natnum = $perfsqr;
+ $Radnum = $number / ($Natnum * $Natnum);
+ #************************************************************
+ # If radnum = 1, the original number is a perfect square
+ # If natnum = 1, it cannot be simplified
+ #************************************************************
+ if ($Radnum == 1) {
+ $Texstr = "$Natnum";
+ } elsif ($Natnum == 1) {
+ $Texstr = "\\sqrt{$number}";
+ } else {
+ $Texstr = "$Natnum \\sqrt{$Radnum}";
+ }
+ } else {
+ ($Natnum, $Radnum, $Error) = 1;
+ $Texstr = "1";
+ }
+ return ($Num, $Natnum, $Radnum, $Texstr, $Error);
+}
diff --git a/macros/AppletObjects.pl b/macros/graph/AppletObjects.pl
similarity index 100%
rename from macros/AppletObjects.pl
rename to macros/graph/AppletObjects.pl
diff --git a/macros/CanvasObject.pl b/macros/graph/CanvasObject.pl
similarity index 100%
rename from macros/CanvasObject.pl
rename to macros/graph/CanvasObject.pl
diff --git a/macros/LiveGraphics3D.pl b/macros/graph/LiveGraphics3D.pl
similarity index 98%
rename from macros/LiveGraphics3D.pl
rename to macros/graph/LiveGraphics3D.pl
index ad64b0d3bb..4eeed59648 100644
--- a/macros/LiveGraphics3D.pl
+++ b/macros/graph/LiveGraphics3D.pl
@@ -26,7 +26,7 @@ =head2 LiveGraphics3D.pl
# Live3Ddata load raw Graphics3D data
# LiveGraphics3D access to all parameters
#
-
+
#
# LiveGraphics3D(options)
#
@@ -58,7 +58,7 @@ =head2 LiveGraphics3D.pl
# or when Java is disabled
#
# tex_size => ratio a scaling factor for the TeX image (as a portion
- # of the line width).
+ # of the line width).
# 1000 is 100%, 500 is 50%, etc.
#
# tex_center => 0 or 1 center the image in TeX mode or not
diff --git a/macros/graph/LiveGraphicsCylindricalPlot3D.pl b/macros/graph/LiveGraphicsCylindricalPlot3D.pl
new file mode 100644
index 0000000000..2abc7ae1fd
--- /dev/null
+++ b/macros/graph/LiveGraphicsCylindricalPlot3D.pl
@@ -0,0 +1,205 @@
+sub _LiveGraphicsCylindricalPlot3D_init { }; # don't reload this file
+
+loadMacros("MathObjects.pl", "LiveGraphics3D.pl");
+
+###########################################################################
+#
+# LiveGraphicsCylindricalPlot3D.pl provides a macros for creating an
+# interactive plot of a function of two variables z = f(r,t)
+# (where r is the radius and t=theta is the angle) via the LiveGraphics3D
+# Java applet. The routine CylindricalPlot3D() takes a MathObject Formula
+# of two variables defined over an annular domain and some plot options
+# as input and returns a string of plot data that can be displayed
+# using the Live3Ddata() routine of the LiveGraphics3D.pl macro.
+#
+# LiveGraphicsCylindricalPlot3D.pl automatically loads
+# MathObjects.pl and LiveGraphics3D.pl.
+#
+###########################################################################
+#
+# The main routine is
+#
+# CylindricalPlot3D
+#
+#
+# Usage: CylindricalPlot3D(options)
+#
+# Options are:
+#
+# function => $f, $f is a MathObjects Formula
+# For example, in the setup section define
+#
+# Context("Numeric");
+# Context()->variables->add(r=>"Real",t=>"Real");
+# $a = random(1,3,1);
+# $f = Formula("$a*r + t"); # use double quotes!
+#
+# before calling CylindricalPlot3D()
+#
+# rvar => "r", independent variable name, default "r"
+# tvar => "t", independent variable name, default "t"
+#
+# rmin => 0, domain for rvar
+# rmax => 3,
+#
+# tmin => -3, domain for tvar
+# tmax => 3,
+#
+# rsamples => 20, deltar = (rmax - rmin) / rsamples
+# tsamples => 20, deltat = (tmax - tmin) / tsamples
+#
+# axesframed => 1, 1 displays framed axes, 0 hides framed axes
+#
+# xaxislabel => "X", Capital letters may be easier to read
+# yaxislabel => "Y",
+# zaxislabel => "Z",
+#
+# outputtype => 1, return string of only polygons (or mesh)
+# 2, return string of only plotoptions
+# 3, return string of polygons (or mesh) and plotoptions
+# 4, return complete plot
+#
+# Happy 3D graphing! -Paul Pearson
+#
+#######################################################################################
+
+$beginplot = "Graphics3D[";
+$endplot = "]";
+
+#############################################
+# Begin CylindricalPlot3D
+
+sub CylindricalPlot3D {
+
+#############################################
+ #
+ # Set default options
+ #
+
+ my %options = (
+ function => Formula("1"),
+ rvar => "r",
+ tvar => "t",
+ rmin => 0.001,
+ rmax => 3,
+ tmin => 0,
+ tmax => 6.28,
+ rsamples => 20,
+ tsamples => 20,
+ axesframed => 1,
+ xaxislabel => "X",
+ yaxislabel => "Y",
+ zaxislabel => "Z",
+ outputtype => 4,
+ @_
+ );
+
+ my $fsubroutine;
+ $options{function}->perlFunction('fsubroutine', [ "$options{rvar}", "$options{tvar}" ]);
+
+######################################################
+ #
+ # Generate a plotdata array, which has two indices
+ #
+
+ my $rsamples1 = $options{rsamples} - 1;
+ my $tsamples1 = $options{tsamples} - 1;
+
+ my $dr = ($options{rmax} - $options{rmin}) / $options{rsamples};
+ my $dt = ($options{tmax} - $options{tmin}) / $options{tsamples};
+
+ my $r;
+ my $t;
+
+ my $x;
+ my $y;
+ my $z;
+
+ foreach my $i (0 .. $options{tsamples}) {
+ $t[$i] = $options{tmin} + $i * $dt;
+ foreach my $j (0 .. $options{rsamples}) {
+ $r[$j] = $options{rmin} + $j * $dr;
+ $x[$i][$j] = $r[$j] * cos($t[$i]);
+ $y[$i][$j] = $r[$j] * sin($t[$i]);
+ $z[$i][$j] = sprintf("%.3f", fsubroutine($r[$j], $t[$i])->value);
+ $x[$i][$j] = sprintf("%.3f", $x[$i][$j]);
+ $y[$i][$j] = sprintf("%.3f", $y[$i][$j]);
+ }
+ }
+
+###########################################################################
+ #
+ # Generate a plotstring from the plotdata.
+ #
+ # The plotstring is a list of polygons that
+ # LiveGraphics3D reads as input.
+ #
+ # For more information on the format of the plotstring, see
+ # http://www.math.umn.edu/~rogness/lg3d/page_NoMathematica.html
+ # http://www.vis.uni-stuttgart.de/~kraus/LiveGraphics3D/documentation.html
+ #
+###########################################
+ #
+ # Generate the polygons in the plotstring
+ #
+
+ my $plotstructure = "{";
+
+ foreach my $i (0 .. $tsamples1) {
+ foreach my $j (0 .. $rsamples1) {
+
+ $plotstructure =
+ $plotstructure
+ . "Polygon[{"
+ . "{$x[$i][$j],$y[$i][$j],$z[$i][$j]},"
+ . "{$x[$i+1][$j],$y[$i+1][$j],$z[$i+1][$j]},"
+ . "{$x[$i+1][$j+1],$y[$i+1][$j+1],$z[$i+1][$j+1]},"
+ . "{$x[$i][$j+1],$y[$i][$j+1],$z[$i][$j+1]}" . "}]";
+
+ if (($i < $tsamples1) || ($j < $rsamples1)) {
+ $plotstructure = $plotstructure . ",";
+ }
+
+ }
+ }
+
+ $plotstructure = $plotstructure . "}";
+
+##############################################
+ #
+ # Add plot options to the plotoptions string
+ #
+
+ my $plotoptions = "";
+
+ if (($options{outputtype} > 1) || ($options{axesframed} == 1)) {
+ $plotoptions =
+ $plotoptions
+ . "Axes->True,AxesLabel->"
+ . "{$options{xaxislabel},$options{yaxislabel},$options{zaxislabel}}";
+ }
+
+####################################################
+ #
+ # Return only the plotstring (if outputtype=>1),
+ # or only plotoptions (if outputtype=>2),
+ # or plotstring, plotoptions (if outputtype=>2),
+ # or the entire plot (default) (if outputtype=>4)
+
+ if ($options{outputtype} == 1) {
+ return $plotstructure;
+ } elsif ($options{outputtype} == 2) {
+ return $plotoptions;
+ } elsif ($options{outputtype} == 3) {
+ return "{" . $plotstructure . "," . $plotoptions . "}";
+ } elsif ($options{outputtype} == 4) {
+ return $beginplot . $plotstructure . "," . $plotoptions . $endplot;
+ } else {
+ return "Invalid outputtype (outputtype should be a number 1 through 4).";
+ }
+
+} # End CylindricalPlot3D
+#####################################################
+#####################################################
+
+1;
diff --git a/macros/graph/LiveGraphicsParametricCurve3D.pl b/macros/graph/LiveGraphicsParametricCurve3D.pl
new file mode 100644
index 0000000000..c6e4defc9c
--- /dev/null
+++ b/macros/graph/LiveGraphicsParametricCurve3D.pl
@@ -0,0 +1,194 @@
+sub _LiveGraphicsParametricCurve3D_init { }; # don't reload this file
+
+loadMacros("MathObjects.pl", "LiveGraphics3D.pl");
+
+###########################################################################
+#
+# LiveGraphicsParametricCurve3D.pl provides a macros for creating an
+# interactive plot of a vector field via the LiveGraphics3D Java applet.
+# The routine ParametricCurve3D() takes three MathObject Formulas of
+# 3 variables as input and returns a string of plot data that can be
+# displayed using the Live3Ddata() routine of the LiveGraphics3D.pl macro.
+#
+# LiveGraphicsParametricCurve3D.pl automatically loads
+# MathObjects.pl and LiveGraphics3D.pl.
+#
+###########################################################################
+#
+# The main routine is
+#
+# ParametricCurve3D()
+#
+#
+# Usage: ParametricCurve3D(options);
+#
+# Options are:
+#
+# Fx => Formula("t*cos(t)"), F = < Fx, Fy, Fz > where Fx, Fy, Fz are each
+# Fy => Formula("t*sin(t)"), functions of tvar
+# Fz => Formula("t"),
+#
+# tvar => "t", independent variable name, default "t"
+# tmin => -3, domain for tvar
+# tmax => 3,
+# tsamples => 3, deltat = (tmax - tmin) / tsamples
+#
+# axesframed => 1, 1 displays framed axes, 0 hides framed axes
+#
+# xaxislabel => "X", Capital letters may be easier to read
+# yaxislabel => "Y",
+# zaxislabel => "Z",
+#
+# orientation => 0, do not show any orientation arrows
+# => 1, show only one arrow in the middle
+# => 2, make the curve entirely of arrows
+#
+# curvecolor => "RGBColor[1.0,0.0,0.0]",
+# curvethickness => 0.001,
+#
+# outputtype => 1, return string of only polygons (or mesh)
+# 2, return string of only plotoptions
+# 3, return string of polygons (or mesh) and plotoptions
+# 4, return complete plot
+#
+# Happy 3D graphing! -Paul Pearson
+#
+#######################################################################################
+
+$beginplot = "Graphics3D[";
+$endplot = "]";
+
+###########################################
+###########################################
+# Begin ParametricCurve3D
+
+sub ParametricCurve3D {
+
+###########################################
+ #
+ # Set default options
+ #
+
+ my %options = (
+ Fx => Formula("1"),
+ Fy => Formula("1"),
+ Fz => Formula("1"),
+ tvar => 't',
+ tmin => -3,
+ tmax => 3,
+ tsamples => 20,
+ orientation => 0,
+ axesframed => 1,
+ xaxislabel => "X",
+ yaxislabel => "Y",
+ zaxislabel => "Z",
+ curvecolor => "RGBColor[1.0,0.0,0.0]",
+ curvethickness => 0.001,
+ outputtype => 4,
+ @_
+ );
+
+ my $Fxsubroutine;
+ my $Fysubroutine;
+ my $Fzsubroutine;
+
+ $options{Fx}->perlFunction('Fxsubroutine', ["$options{tvar}"]);
+ $options{Fy}->perlFunction('Fysubroutine', ["$options{tvar}"]);
+ $options{Fz}->perlFunction('Fzsubroutine', ["$options{tvar}"]);
+
+######################################################
+ #
+ # Generate plot data
+ #
+
+ my $dt = ($options{tmax} - $options{tmin}) / $options{tsamples};
+
+ my $t;
+
+ # The curve data
+ foreach my $i (0 .. $options{tsamples}) {
+ $t[$i] = $options{tmin} + $i * $dt;
+
+ $FX[$i] = sprintf("%.3f", (Fxsubroutine($t[$i])->value));
+ $FY[$i] = sprintf("%.3f", (Fysubroutine($t[$i])->value));
+ $FZ[$i] = sprintf("%.3f", (Fzsubroutine($t[$i])->value));
+
+ }
+
+ if ($options{orientation} > 0) {
+ #
+ # The arrow head data
+ #
+ my $tmidindex = sprintf("%.0f", $options{tsamples} / 2);
+
+ }
+
+###########################################################################
+ #
+ # Generate plotstructure from the plotdata.
+ #
+ # The plotstucture is a list of arrows (made of lines) that
+ # LiveGraphics3D reads as input.
+ #
+ # For more information on the format of the plotstructure, see
+ # http://www.math.umn.edu/~rogness/lg3d/page_NoMathematica.html
+ # http://www.vis.uni-stuttgart.de/~kraus/LiveGraphics3D/documentation.html
+ #
+###########################################
+ #
+ # Generate the line segments in the plotstructure
+ #
+
+ my $plotstructure = "{$options{curvecolor},Thickness[$options{curvethickness}],";
+
+ my $tsamples1 = $options{tsamples} - 1;
+
+ foreach my $i (0 .. $tsamples1) {
+
+ $plotstructure =
+ $plotstructure . "Line[{" . "{$FX[$i],$FY[$i],$FZ[$i]}," . "{$FX[$i+1],$FY[$i+1],$FZ[$i+1]}" . "}]";
+
+ if ($i < $tsamples1) { $plotstructure = $plotstructure . "," }
+
+ }
+
+ $plotstructure = $plotstructure . "}";
+
+##############################################
+ #
+ # Add plot options to the plotoptions string
+ #
+
+ my $plotoptions = "";
+
+ if (($options{outputtype} > 1) || ($options{axesframed} == 1)) {
+ $plotoptions =
+ $plotoptions
+ . "Axes->True,AxesLabel->"
+ . "{$options{xaxislabel},$options{yaxislabel},$options{zaxislabel}}";
+ }
+
+####################################################
+ #
+ # Return only the plotstring (if outputtype=>1),
+ # or only plotoptions (if outputtype=>2),
+ # or plotstring, plotoptions (if outputtype=>2),
+ # or the entire plot (default) (if outputtype=>4)
+
+ if ($options{outputtype} == 1) {
+ return $plotstructure;
+ } elsif ($options{outputtype} == 2) {
+ return $plotoptions;
+ } elsif ($options{outputtype} == 3) {
+ return "{" . $plotstructure . "," . $plotoptions . "}";
+ } elsif ($options{outputtype} == 4) {
+ return $beginplot . $plotstructure . "," . $plotoptions . $endplot;
+ } else {
+ return "Invalid outputtype (outputtype should be a number 1 through 4).";
+ }
+
+} # End ParametricCurve3D
+##############################################
+##############################################
+
+1;
diff --git a/macros/graph/LiveGraphicsParametricSurface3D.pl b/macros/graph/LiveGraphicsParametricSurface3D.pl
new file mode 100644
index 0000000000..dd8b712ad8
--- /dev/null
+++ b/macros/graph/LiveGraphicsParametricSurface3D.pl
@@ -0,0 +1,257 @@
+sub _LiveGraphicsParametricSurface3D_init { }; # don't reload this file
+
+loadMacros("MathObjects.pl", "LiveGraphics3D.pl");
+
+###########################################################################
+#
+# LiveGraphicsParametricSurface3D.pl provides a macro for creating an
+# interactive plot of a parametric surface via the LiveGraphics3D Java applet.
+# The routine ParametricSurface3D() takes three MathObject Formulas of
+# 2 variables as input and returns a string of plot data that can be
+# displayed using the Live3Ddata() routine of the LiveGraphics3D.pl macro.
+#
+# LiveGraphicsParametricSurface3D.pl automatically loads
+# MathObjects.pl and LiveGraphics3D.pl.
+#
+###########################################################################
+#
+# The main routine is
+#
+# ParametricSurface3D()
+#
+#
+# Usage: ParametricSurface3D(options);
+#
+# Options are:
+#
+# Fx => Formula("cos(u)*cos(v)"), x-coordinate function
+# Fy => Formula("sin(u)*cos(v)"), y-coordinate function
+# Fz => Formula("sin(v)"), z-coordinate function
+# F(u,v) = < Fx, Fy, Fz >
+# = < Fx(u,v), Fy(u,v), Fz(u,v) >
+#
+# uvar => "u", parameter name, default "u"
+# vvar => "v", parameter name, default "v"
+#
+# umin => -3, domain for uvar
+# umax => 3,
+#
+# vmin => -3, domain for vvar
+# vmax => 3,
+#
+# usamples => 3, deltau = (umax - umin) / usamples
+# vsamples => 3, deltav = (vmax - vmin) / vsamples
+#
+# axesframed => 1, 1 displays framed axes, 0 hides framed axes
+#
+# xaxislabel => "X", Capital letters may be easier to read
+# yaxislabel => "Y",
+# zaxislabel => "Z",
+#
+# edges => 0, 1 displays edges of polygons, 0 hides them
+# edgecolor => "RGBColor[0.2,0.2,0.2]",
+# edgethickness => "Thickness[0.001]",
+#
+# mesh => 0, 1 displays open mesh, 0 displays filled polygons
+# meshcolor => "RGBColor[0.7,0.7,0.7]", three values between 0 and 1
+# meshthickness => 0.001,
+#
+# outputtype => 1, return string of only polygons (or mesh)
+# 2, return string of only plotoptions
+# 3, return string of polygons (or mesh) and plotoptions
+# 4, return complete plot
+#
+# Happy 3D graphing! -Paul Pearson
+#
+#######################################################################################
+
+$beginplot = "Graphics3D[";
+$endplot = "]";
+
+###########################################
+###########################################
+# Begin ParametricSurface3D
+
+sub ParametricSurface3D {
+
+###########################################
+ #
+ # Set default options
+ #
+
+ my %options = (
+ Fx => Formula("1"),
+ Fy => Formula("1"),
+ Fz => Formula("1"),
+ uvar => 'u',
+ vvar => 'v',
+ umin => -3,
+ umax => 3,
+ vmin => -3,
+ vmax => 3,
+ usamples => 20,
+ vsamples => 20,
+ axesframed => 1,
+ xaxislabel => "X",
+ yaxislabel => "Y",
+ zaxislabel => "Z",
+ edges => 0,
+ edgecolor => "RGBColor[0.2,0.2,0.2]",
+ edgethickness => "Thickness[0.001]",
+ mesh => 0,
+ meshcolor => "RGBColor[0.7,0.7,0.7]",
+ meshthickness => 0.001,
+ outputtype => 4,
+ @_
+ );
+
+ my $Fxsubroutine;
+ my $Fysubroutine;
+ my $Fzsubroutine;
+
+ $options{Fx}->perlFunction('Fxsubroutine', [ "$options{uvar}", "$options{vvar}" ]);
+ $options{Fy}->perlFunction('Fysubroutine', [ "$options{uvar}", "$options{vvar}" ]);
+ $options{Fz}->perlFunction('Fzsubroutine', [ "$options{uvar}", "$options{vvar}" ]);
+
+######################################################
+ #
+ # Generate plot data
+ #
+
+ my $du = ($options{umax} - $options{umin}) / $options{usamples};
+ my $dv = ($options{vmax} - $options{vmin}) / $options{vsamples};
+
+ my $u;
+ my $v;
+
+ foreach my $i (0 .. $options{usamples}) {
+ $u[$i] = $options{umin} + $i * $du;
+ foreach my $j (0 .. $options{vsamples}) {
+ $v[$j] = $options{vmin} + $j * $dv;
+
+ $FX[$i][$j] = sprintf("%.3f", (Fxsubroutine($u[$i], $v[$j])->value));
+ $FY[$i][$j] = sprintf("%.3f", (Fysubroutine($u[$i], $v[$j])->value));
+ $FZ[$i][$j] = sprintf("%.3f", (Fzsubroutine($u[$i], $v[$j])->value));
+
+ $u[$i] = sprintf("%.3f", $u[$i]);
+ $v[$j] = sprintf("%.3f", $v[$j]);
+ }
+ }
+
+###########################################################################
+ #
+ # Generate plotstructure from the plotdata.
+ #
+ # The plotstucture is a list of arrows (made of lines) that
+ # LiveGraphics3D reads as input.
+ #
+ # For more information on the format of the plotstructure, see
+ # http://www.math.umn.edu/~rogness/lg3d/page_NoMathematica.html
+ # http://www.vis.uni-stuttgart.de/~kraus/LiveGraphics3D/documentation.html
+ #
+###########################################
+ #
+ # Generate the polygons in the plotstructure
+ #
+
+ my $plotstructure = "{";
+
+ if ($options{edges} == 0 && $options{mesh} == 0) {
+ $plotstructure = $plotstructure . "EdgeForm[],";
+ } elsif ($options{edges} == 1 && $options{mesh} == 0) {
+ $plotstructure = $plotstructure . "EdgeForm[{$options{edgecolor},$options{edgethickness}}],";
+ }
+
+ if ($options{mesh} == 1) {
+ $plotstructure = $plotstructure . "$options{meshcolor},Thickness[$options{meshthickness}],";
+ }
+
+ my $usamples1 = $options{usamples} - 1;
+ my $vsamples1 = $options{vsamples} - 1;
+
+ if ($options{mesh} == 0) {
+
+ foreach my $i (0 .. $usamples1) {
+ foreach my $j (0 .. $vsamples1) {
+
+ $plotstructure =
+ $plotstructure
+ . "Polygon[{"
+ . "{$FX[$i][$j],$FY[$i][$j],$FZ[$i][$j]},"
+ . "{$FX[$i+1][$j],$FY[$i+1][$j],$FZ[$i+1][$j]},"
+ . "{$FX[$i+1][$j+1],$FY[$i+1][$j+1],$FZ[$i+1][$j+1]},"
+ . "{$FX[$i][$j+1],$FY[$i][$j+1],$FZ[$i][$j+1]}" . "}]";
+
+ if (($i < $usamples1) || ($j < $vsamples1)) {
+ $plotstructure = $plotstructure . ",";
+ }
+
+ }
+ }
+
+ # end mesh == 0
+ } else {
+ # begin mesh == 1
+
+ foreach my $i (0 .. $usamples1) {
+ foreach my $j (0 .. $vsamples1) {
+
+ # this could be made more efficient
+ $plotstructure =
+ $plotstructure
+ . "Line[{"
+ . "{$FX[$i][$j],$FY[$i][$j],$FZ[$i][$j]},"
+ . "{$FX[$i+1][$j],$FY[$i+1][$j],$FZ[$i+1][$j]},"
+ . "{$FX[$i+1][$j+1],$FY[$i+1][$j+1],$FZ[$i+1][$j+1]},"
+ . "{$FX[$i][$j+1],$FY[$i][$j+1],$FZ[$i][$j+1]},"
+ . "{$FX[$i][$j],$FY[$i][$j],$FZ[$i][$j]}" . "}]";
+
+ if (($i < $usamples1) || ($j < $vsamples1)) {
+ $plotstructure = $plotstructure . ",";
+ }
+
+ }
+ }
+
+ } # end mesh == 1
+
+ $plotstructure = $plotstructure . "}";
+
+##############################################
+ #
+ # Add plot options to the plotoptions string
+ #
+
+ my $plotoptions = "";
+
+ if (($options{outputtype} > 1) || ($options{axesframed} == 1)) {
+ $plotoptions =
+ $plotoptions
+ . "Axes->True,AxesLabel->"
+ . "{$options{xaxislabel},$options{yaxislabel},$options{zaxislabel}}";
+ }
+
+####################################################
+ #
+ # Return only the plotstring (if outputtype=>1),
+ # or only plotoptions (if outputtype=>2),
+ # or plotstring, plotoptions (if outputtype=>2),
+ # or the entire plot (default) (if outputtype=>4)
+
+ if ($options{outputtype} == 1) {
+ return $plotstructure;
+ } elsif ($options{outputtype} == 2) {
+ return $plotoptions;
+ } elsif ($options{outputtype} == 3) {
+ return "{" . $plotstructure . "," . $plotoptions . "}";
+ } elsif ($options{outputtype} == 4) {
+ return $beginplot . $plotstructure . "," . $plotoptions . $endplot;
+ } else {
+ return "Invalid outputtype (outputtype should be a number 1 through 4).";
+ }
+
+} # End ParametricSurface3D
+##############################################
+##############################################
+
+1;
diff --git a/macros/graph/LiveGraphicsRectangularPlot3D.pl b/macros/graph/LiveGraphicsRectangularPlot3D.pl
new file mode 100644
index 0000000000..708f687048
--- /dev/null
+++ b/macros/graph/LiveGraphicsRectangularPlot3D.pl
@@ -0,0 +1,416 @@
+sub _LiveGraphicsRectangularPlot3D_init { }; # don't reload this file
+
+loadMacros("MathObjects.pl", "LiveGraphics3D.pl");
+
+###########################################################################
+#
+# LiveGraphicsRectangularPlot3D.pl provides two macros for creating an
+# interactive plot of a function of two variables z = f(x,y) in
+# Rectangular (Cartesian) coordinates via the LiveGraphics3D Java applet.
+# The routine RectangularDomainPlot3D() takes a MathObject Formula of
+# two variables defined over a rectangular domain and some plot options
+# as input and returns a string of plot data that can be displayed
+# using the Live3Ddata() routine of the LiveGraphics3D.pl macro.
+# The routine AnnularDomainPlot3D works similarly for a function
+# z = f(x,y) over an annular domain specified in polar by
+# rmin < r < rmax and tmin < theta < tmax.
+#
+# LiveGraphicsRectangularPlot3D.pl automatically loads
+# MathObjects.pl and LiveGraphics3D.pl.
+#
+###########################################################################
+#
+# The first routine is
+#
+# RectangularPlot3DRectangularDomain
+#
+#
+# Usage: RectangularPlot3DRectangularDomain(options);
+#
+# Options are:
+#
+# function => $f, $f is a MathObjects Formula
+# For example, in the setup section define
+#
+# Context("Numeric");
+# Context()->variables->add(s=>"Real",t=>"Real");
+# $a = random(1,3,1);
+# $f = Formula("$a*s^2-2*t"); # use double quotes!
+#
+# before calling RectangularPlot3DRectangularDomain()
+#
+# xvar => "s", independent variable name, default "x"
+# yvar => "t", independent variable name, default "y"
+#
+# xmin => -3, domain for xvar
+# xmax => 3,
+#
+# ymin => -3, domain for yvar
+# ymax => 3,
+#
+# xsamples => 20, deltax = (xmax - xmin) / xsamples
+# ysamples => 20, deltay = (ymax - ymin) / ysamples
+#
+# axesframed => 1, 1 displays framed axes, 0 hides framed axes
+#
+# xaxislabel => "S", Capital letters may be easier to read
+# yaxislabel => "T",
+# zaxislabel => "Z",
+#
+# outputtype => 1, return string of only polygons (or mesh)
+# 2, return string of only plotoptions
+# 3, return string of polygons (or mesh) and plotoptions
+# 4, return complete plot
+#
+#######################################################################################
+#######################################################################################
+#
+# The second routine is
+#
+# RectangularPlot3DAnnularDomain create a plotstring from a function
+#
+#
+# Usage: RectangularPlot3DAnnularDomain(options);
+#
+# Options are:
+#
+# function => $f, $f is a MathObjects Formula
+# For example, in the setup section define
+#
+# Context("Numeric");
+# Context()->variables->add(y=>"Real",r=>"Real",t=>"Real");
+# $a = random(1,3,1);
+# $f = Formula("$a*e^(- x^2 - y^2)"); # use double quotes!
+#
+# before calling RectangularPlot3DAnnularDomain()
+#
+# xvar => "x", independent variable name, default "x"
+# yvar => "y", independent variable name, default "y"
+#
+# rvar => "r", independent variable name, default "r"
+# tvar => "t", independent variable name, default "t" (for theta)
+#
+# rmin => -3, domain for rvar
+# rmax => 3,
+#
+# tmin => -3, domain for tvar
+# tmax => 3,
+#
+# rsamples => 20, deltar = (rmax - rmin) / rsamples
+# tsamples => 20, deltat = (tmax - tmin) / tsamples
+#
+# axesframed => 1, 1 displays framed axes, 0 hides framed axes
+#
+# xaxislabel => "X", Capital letters may be easier to read
+# yaxislabel => "Y",
+# zaxislabel => "Z",
+#
+# outputtype => 1, return string of only polygons (or mesh)
+# 2, return string of only plotoptions
+# 3, return string of polygons (or mesh) and plotoptions
+# 4, return complete plot
+#
+# Happy 3D graphing! - Paul Pearson
+#
+
+$beginplot = "Graphics3D[";
+$endplot = "]";
+
+###########################################
+###########################################
+# Begin RectangularPlot3DRectangularDomain
+
+sub RectangularPlot3DRectangularDomain {
+
+###########################################
+ #
+ # Set default options
+ #
+
+ my %options = (
+ function => Formula("1"),
+ xvar => 'x',
+ yvar => 'y',
+ xmin => -3,
+ xmax => 3,
+ ymin => -3,
+ ymax => 3,
+ xsamples => 20,
+ ysamples => 20,
+ axesframed => 1,
+ xaxislabel => "X",
+ yaxislabel => "Y",
+ zaxislabel => "Z",
+ outputtype => 4,
+ @_
+ );
+
+############################################
+ #
+ # Reset to Context("Numeric") just to be
+ # sure that everything will work properly.
+ #
+
+ #Context("Numeric");
+ #Context()->variables->are($options{xvar}=>"Real",$options{yvar}=>"Real");
+
+ my $fsubroutine;
+ $options{function}->perlFunction('fsubroutine', [ "$options{xvar}", "$options{yvar}" ]);
+
+######################################################
+ #
+ # Generate a plotdata array, which has two indices
+ #
+
+ my $xsamples1 = $options{xsamples} - 1;
+ my $ysamples1 = $options{ysamples} - 1;
+
+ my $dx = ($options{xmax} - $options{xmin}) / $options{xsamples};
+ my $dy = ($options{ymax} - $options{ymin}) / $options{ysamples};
+
+ my $x;
+ my $y;
+
+ my $z;
+
+ foreach my $i (0 .. $options{xsamples}) {
+ $x[$i] = $options{xmin} + $i * $dx;
+ foreach my $j (0 .. $options{ysamples}) {
+ $y[$j] = $options{ymin} + $j * $dy;
+ # Use sprintf to round to three decimal places
+ $z[$i][$j] = sprintf("%.3f", fsubroutine($x[$i], $y[$j])->value);
+ $y[$j] = sprintf("%.3f", $y[$j]);
+ }
+ $x[$i] = sprintf("%.3f", $x[$i]);
+ }
+
+###########################################################################
+ #
+ # Generate a plotstring from the plotdata.
+ #
+ # The plotstring is a list of polygons
+ # LiveGraphics3D reads as input.
+ #
+ # For more information on the format of the plotstring, see
+ # http://www.math.umn.edu/~rogness/lg3d/page_NoMathematica.html
+ # http://www.vis.uni-stuttgart.de/~kraus/LiveGraphics3D/documentation.html
+ #
+###########################################
+ #
+ # Generate the polygons in the plotstring
+ #
+
+ my $plotstructure = "{";
+
+ foreach my $i (0 .. $xsamples1) {
+ foreach my $j (0 .. $ysamples1) {
+
+ $plotstructure =
+ $plotstructure
+ . "Polygon[{"
+ . "{$x[$i],$y[$j],$z[$i][$j]},"
+ . "{$x[$i+1],$y[$j],$z[$i+1][$j]},"
+ . "{$x[$i+1],$y[$j+1],$z[$i+1][$j+1]},"
+ . "{$x[$i],$y[$j+1],$z[$i][$j+1]}" . "}]";
+
+ if (($i < $xsamples1) || ($j < $ysamples1)) {
+ $plotstructure = $plotstructure . ",";
+ }
+
+ }
+ }
+
+ $plotstructure = $plotstructure . "}";
+
+##############################################
+ #
+ # Add plot options to the plotoptions string
+ #
+
+ my $plotoptions = "";
+
+ if (($options{outputtype} > 1) || ($options{axesframed} == 1)) {
+ $plotoptions =
+ $plotoptions
+ . "Axes->True,AxesLabel->"
+ . "{$options{xaxislabel},$options{yaxislabel},$options{zaxislabel}}";
+ }
+
+####################################################
+ #
+ # Return only the plotstring (if outputtype=>1),
+ # or only plotoptions (if outputtype=>2),
+ # or plotstring, plotoptions (if outputtype=>2),
+ # or the entire plot (default) (if outputtype=>4)
+
+ if ($options{outputtype} == 1) {
+ return $plotstructure;
+ } elsif ($options{outputtype} == 2) {
+ return $plotoptions;
+ } elsif ($options{outputtype} == 3) {
+ return "{" . $plotstructure . "," . $plotoptions . "}";
+ } elsif ($options{outputtype} == 4) {
+ return $beginplot . $plotstructure . "," . $plotoptions . $endplot;
+ } else {
+ return "Invalid outputtype (outputtype should be a number 1 through 4).";
+ }
+
+} # End RectangularPlot3DRectangularDomain
+##############################################
+##############################################
+
+#############################################
+#############################################
+# Begin RectangularPlot3DAnnularDomain
+
+sub RectangularPlot3DAnnularDomain {
+
+#############################################
+ #
+ # Set default options
+ #
+
+ my %options = (
+ function => Formula("1"),
+ xvar => "x",
+ yvar => "y",
+ rvar => "r",
+ tvar => "t",
+ rmin => 0.001,
+ rmax => 3,
+ tmin => 0,
+ tmax => 6.28,
+ rsamples => 20,
+ tsamples => 20,
+ axesframed => 1,
+ xaxislabel => "X",
+ yaxislabel => "Y",
+ zaxislabel => "Z",
+ outputtype => 4,
+ @_
+ );
+
+############################################
+ #
+ # Reset to Context("Numeric") just to be
+ # sure that everything will work properly.
+ #
+
+ #Context("Numeric");
+ #Context()->variables->are(
+ #$options{xvar}=>"Real",
+ #$options{yvar}=>"Real",
+ #$options{rvar}=>"Real",
+ #$options{tvar}=>"Real"
+ #);
+
+ my $fsubroutine;
+ $options{function}->perlFunction('fsubroutine', [ "$options{xvar}", "$options{yvar}" ]);
+
+######################################################
+ #
+ # Generate a plotdata array, which has two indices
+ #
+
+ my $rsamples1 = $options{rsamples} - 1;
+ my $tsamples1 = $options{tsamples} - 1;
+
+ my $dr = ($options{rmax} - $options{rmin}) / $options{rsamples};
+ my $dt = ($options{tmax} - $options{tmin}) / $options{tsamples};
+
+ my $t;
+ my $r;
+
+ my $x;
+ my $y;
+
+ my $z;
+
+ foreach my $i (0 .. $options{tsamples}) {
+ $t[$i] = $options{tmin} + $i * $dt;
+ foreach my $j (0 .. $options{rsamples}) {
+ $r[$j] = $options{rmin} + $j * $dr;
+ $x[$i][$j] = $r[$j] * cos($t[$i]);
+ $y[$i][$j] = $r[$j] * sin($t[$i]);
+ $z[$i][$j] = sprintf("%.3f", fsubroutine($x[$i][$j], $y[$i][$j])->value);
+ $x[$i][$j] = sprintf("%.3f", $x[$i][$j]);
+ $y[$i][$j] = sprintf("%.3f", $y[$i][$j]);
+ }
+ }
+
+###########################################################################
+ #
+ # Generate a plotstring from the plotdata.
+ #
+ # The plotstring is a list of polygons that
+ # LiveGraphics3D reads as input.
+ #
+ # For more information on the format of the plotstring, see
+ # http://www.math.umn.edu/~rogness/lg3d/page_NoMathematica.html
+ # http://www.vis.uni-stuttgart.de/~kraus/LiveGraphics3D/documentation.html
+ #
+###########################################
+ #
+ # Generate the polygons in the plotstring
+ #
+
+ my $plotstructure = "{";
+
+ foreach my $i (0 .. $tsamples1) {
+ foreach my $j (0 .. $rsamples1) {
+
+ $plotstructure =
+ $plotstructure
+ . "Polygon[{"
+ . "{$x[$i][$j],$y[$i][$j],$z[$i][$j]},"
+ . "{$x[$i+1][$j],$y[$i+1][$j],$z[$i+1][$j]},"
+ . "{$x[$i+1][$j+1],$y[$i+1][$j+1],$z[$i+1][$j+1]},"
+ . "{$x[$i][$j+1],$y[$i][$j+1],$z[$i][$j+1]}" . "}]";
+
+ if (($i < $tsamples1) || ($j < $rsamples1)) {
+ $plotstructure = $plotstructure . ",";
+ }
+
+ }
+ }
+
+ $plotstructure = $plotstructure . "}";
+
+##############################################
+ #
+ # Add plot options to the plotoptions string
+ #
+
+ my $plotoptions = "";
+
+ if (($options{outputtype} > 1) || ($options{axesframed} == 1)) {
+ $plotoptions =
+ $plotoptions
+ . "Axes->True,AxesLabel->"
+ . "{$options{xaxislabel},$options{yaxislabel},$options{zaxislabel}}";
+ }
+
+####################################################
+ #
+ # Return only the plotstring (if outputtype=>1),
+ # or only plotoptions (if outputtype=>2),
+ # or plotstring, plotoptions (if outputtype=>2),
+ # or the entire plot (default) (if outputtype=>4)
+
+ if ($options{outputtype} == 1) {
+ return $plotstructure;
+ } elsif ($options{outputtype} == 2) {
+ return $plotoptions;
+ } elsif ($options{outputtype} == 3) {
+ return "{" . $plotstructure . "," . $plotoptions . "}";
+ } elsif ($options{outputtype} == 4) {
+ return $beginplot . $plotstructure . "," . $plotoptions . $endplot;
+ } else {
+ return "Invalid outputtype (outputtype should be a number 1 through 4).";
+ }
+
+} # End RectangularPlot3DAnnularDomain
+#####################################################
+#####################################################
+
+1;
diff --git a/macros/graph/LiveGraphicsVectorField2D.pl b/macros/graph/LiveGraphicsVectorField2D.pl
new file mode 100644
index 0000000000..6620c2d0a9
--- /dev/null
+++ b/macros/graph/LiveGraphicsVectorField2D.pl
@@ -0,0 +1,218 @@
+sub _LiveGraphicsVectorField2D_init { }; # don't reload this file
+
+loadMacros("MathObjects.pl", "LiveGraphics3D.pl");
+
+###########################################################################
+#
+# LiveGraphicsVectorField2D.pl provides a macros for creating an
+# interactive plot of a vector field via the LiveGraphics3D Java applet.
+# The routine VectorField2D() takes two MathObject Formulas of
+# 2 variables as input and returns a string of plot data that can be
+# displayed using the Live3Ddata() routine of the LiveGraphics3D.pl macro.
+#
+# LiveGraphicsVectorField2D.pl automatically loads
+# MathObjects.pl and LiveGraphics3D.pl.
+#
+###########################################################################
+#
+# The main routine is
+#
+# VectorField2D()
+#
+#
+# Usage: VectorField2D(options);
+#
+# Options are:
+#
+# Fx => Formula("y"), F = < Fx, Fy, Fz > where Fx, Fy, Fz are each
+# Fy => Formula("-x"), functions of 3 variables
+#
+# xvar => "r", independent variable name, default "x"
+# yvar => "s", independent variable name, default "y"
+#
+# xmin => -3, domain for xvar
+# xmax => 3,
+#
+# ymin => -3, domain for yvar
+# ymax => 3,
+#
+# xsamples => 3, deltax = (xmax - xmin) / xsamples
+# ysamples => 3, deltay = (ymax - ymin) / ysamples
+#
+# axesframed => 1, 1 displays framed axes, 0 hides framed axes
+#
+# xaxislabel => "R", Capital letters may be easier to read
+# yaxislabel => "S",
+#
+# vectorcolor => "RGBColor[1.0,0.0,0.0]",
+# vectorscale => 0.2,
+# vectorthickness => 0.001,
+#
+# outputtype => 1, return string of only polygons (or mesh)
+# 2, return string of only plotoptions
+# 3, return string of polygons (or mesh) and plotoptions
+# 4, return complete plot
+#
+# Happy 2D graphing! -Paul Pearson
+#
+#######################################################################################
+
+$beginplot = "Graphics3D[";
+$endplot = "]";
+
+###########################################
+###########################################
+# Begin VectorField2D
+
+sub VectorField2D {
+
+###########################################
+ #
+ # Set default options
+ #
+
+ my %options = (
+ Fx => Formula("1"),
+ Fy => Formula("1"),
+ xvar => 'x',
+ yvar => 'y',
+ xmin => -3,
+ xmax => 3,
+ ymin => -3,
+ ymax => 3,
+ xsamples => 20,
+ ysamples => 20,
+ axesframed => 1,
+ xaxislabel => "X",
+ yaxislabel => "Y",
+ vectorcolor => "RGBColor[1.0,0.0,0.0]",
+ vectorscale => 0.2,
+ vectorthickness => 0.001,
+ outputtype => 4,
+ @_
+ );
+
+ my $Fxsubroutine;
+ my $Fysubroutine;
+
+ $options{Fx}->perlFunction('Fxsubroutine', [ "$options{xvar}", "$options{yvar}" ]);
+ $options{Fy}->perlFunction('Fysubroutine', [ "$options{xvar}", "$options{yvar}" ]);
+
+######################################################
+ #
+ # Generate plot data
+ #
+
+ my $dx = ($options{xmax} - $options{xmin}) / $options{xsamples};
+ my $dy = ($options{ymax} - $options{ymin}) / $options{ysamples};
+
+ my $xtail;
+ my $ytail;
+
+ foreach my $i (0 .. $options{xsamples}) {
+ $xtail[$i] = $options{xmin} + $i * $dx;
+ foreach my $j (0 .. $options{ysamples}) {
+ $ytail[$j] = $options{ymin} + $j * $dy;
+
+ $FX[$i][$j] = sprintf("%.3f", $options{vectorscale} * (Fxsubroutine($xtail[$i], $ytail[$j])->value));
+ $FY[$i][$j] = sprintf("%.3f", $options{vectorscale} * (Fysubroutine($xtail[$i], $ytail[$j])->value));
+
+ $xtail[$i] = sprintf("%.3f", $xtail[$i]);
+ $ytail[$j] = sprintf("%.3f", $ytail[$j]);
+
+ $xtip[$i][$j] = $xtail[$i] + sprintf("%.3f", $FX[$i][$j]);
+ $ytip[$i][$j] = $ytail[$j] + sprintf("%.3f", $FY[$i][$j]);
+
+ $xleftbarb[$i][$j] = sprintf("%.3f", $xtail[$i] + 0.8 * $FX[$i][$j] - 0.2 * $FY[$i][$j]);
+ $yleftbarb[$i][$j] = sprintf("%.3f", $ytail[$j] + 0.8 * $FY[$i][$j] + 0.2 * $FX[$i][$j]);
+
+ $xrightbarb[$i][$j] = sprintf("%.3f", $xtail[$i] + 0.8 * $FX[$i][$j] + 0.2 * $FY[$i][$j]);
+ $yrightbarb[$i][$j] = sprintf("%.3f", $ytail[$j] + 0.8 * $FY[$i][$j] - 0.2 * $FX[$i][$j]);
+ }
+ }
+
+###########################################################################
+ #
+ # Generate plotstructure from the plotdata.
+ #
+ # The plotstucture is a list of arrows (made of lines) that
+ # LiveGraphics3D reads as input.
+ #
+ # For more information on the format of the plotstructure, see
+ # http://www.math.umn.edu/~rogness/lg3d/page_NoMathematica.html
+ # http://www.vis.uni-stuttgart.de/~kraus/LiveGraphics3D/documentation.html
+ #
+###########################################
+ #
+ # Generate the polygons in the plotstructure
+ #
+
+ my $plotstructure = "{{{{$options{vectorcolor},EdgeForm[],Thickness[$options{vectorthickness}],";
+
+ foreach my $i (0 .. $options{xsamples}) {
+ foreach my $j (0 .. $options{ysamples}) {
+
+ $plotstructure =
+ $plotstructure
+ . "Line[{"
+ . "{$xtail[$i],$ytail[$j],0},"
+ . "{$xtip[$i][$j],$ytip[$i][$j],0}" . "}],"
+ . "Line[{"
+ . "{$xleftbarb[$i][$j],$yleftbarb[$i][$j],0},"
+ . "{$xtip[$i][$j],$ytip[$i][$j],0},"
+ . "{$xrightbarb[$i][$j],$yrightbarb[$i][$j],0}" . "}]";
+
+ if (($i < $options{xsamples}) || ($j < $options{ysamples})) {
+ $plotstructure = $plotstructure . ",";
+ }
+
+ }
+ }
+
+ $plotstructure = $plotstructure . "}}}}";
+
+##############################################
+ #
+ # Add plot options to the plotoptions string
+ #
+
+ my $plotoptions = "";
+
+ if (($options{outputtype} > 1) || ($options{axesframed} == 1)) {
+
+ $plotoptions =
+ $plotoptions
+ . "PlotRange->{{$options{xmin},$options{xmax}},{$options{ymin},$options{ymax}},{-0.1,0.1}},"
+ . "ViewPoint->{0,0,1000},"
+ . "ViewVertical->{0,1,0},"
+ . "Lighting->False,"
+ . "AxesLabel->{$options{xaxislabel},$options{yaxislabel},Z},"
+ . #; # .
+ "Axes->{True,True,False}";
+
+ }
+
+####################################################
+ #
+ # Return only the plotstring (if outputtype=>1),
+ # or only plotoptions (if outputtype=>2),
+ # or plotstring, plotoptions (if outputtype=>2),
+ # or the entire plot (default) (if outputtype=>4)
+
+ if ($options{outputtype} == 1) {
+ return $plotstructure;
+ } elsif ($options{outputtype} == 2) {
+ return $plotoptions;
+ } elsif ($options{outputtype} == 3) {
+ return "{" . $plotstructure . "," . $plotoptions . "}";
+ } elsif ($options{outputtype} == 4) {
+ return $beginplot . $plotstructure . "," . $plotoptions . $endplot;
+ } else {
+ return "Invalid outputtype (outputtype should be a number 1 through 4).";
+ }
+
+} # End VectorField2D
+##############################################
+##############################################
+
+1;
diff --git a/macros/graph/LiveGraphicsVectorField3D.pl b/macros/graph/LiveGraphicsVectorField3D.pl
new file mode 100644
index 0000000000..88c6d6471e
--- /dev/null
+++ b/macros/graph/LiveGraphicsVectorField3D.pl
@@ -0,0 +1,255 @@
+sub _LiveGraphicsVectorField3D_init { }; # don't reload this file
+
+loadMacros("MathObjects.pl", "LiveGraphics3D.pl");
+
+###########################################################################
+#
+# LiveGraphicsVectorField3D.pl provides a macros for creating an
+# interactive plot of a vector field via the LiveGraphics3D Java applet.
+# The routine VectorField3D() takes three MathObject Formulas of
+# 3 variables as input and returns a string of plot data that can be
+# displayed using the Live3Ddata() routine of the LiveGraphics3D.pl macro.
+#
+# LiveGraphicsVectorField3D.pl automatically loads
+# MathObjects.pl and LiveGraphics3D.pl.
+#
+###########################################################################
+#
+# The main routine is
+#
+# VectorField3D()
+#
+#
+# Usage: VectorField3D(options);
+#
+# Options are:
+#
+# Fx => Formula("y"), F = < Fx, Fy, Fz > where Fx, Fy, Fz are each
+# Fy => Formula("-x"), functions of 3 variables
+# Fz => Formula("z"),
+#
+# xvar => "r", independent variable name, default "x"
+# yvar => "s", independent variable name, default "y"
+# zvar => "t", independent variable name, default "z"
+#
+# xmin => -3, domain for xvar
+# xmax => 3,
+#
+# ymin => -3, domain for yvar
+# ymax => 3,
+#
+# zmin => -3, domain for zvar
+# zmax => 3,
+#
+# xsamples => 3, deltax = (xmax - xmin) / xsamples
+# ysamples => 3, deltay = (ymax - ymin) / ysamples
+# zsamples => 3, deltaz = (zmax - zmin) / zsamples
+#
+# axesframed => 1, 1 displays framed axes, 0 hides framed axes
+#
+# xaxislabel => "R", Capital letters may be easier to read
+# yaxislabel => "S",
+# zaxislabel => "T",
+#
+# vectorcolor => "RGBColor[0.0,0.0,1.0]",
+# vectorscale => 0.2,
+# vectorthickness => 0.001,
+#
+# outputtype => 1, return string of only polygons (or mesh)
+# 2, return string of only plotoptions
+# 3, return string of polygons (or mesh) and plotoptions
+# 4, return complete plot
+#
+# Happy 3D graphing! -Paul Pearson
+#
+#######################################################################################
+
+$beginplot = "Graphics3D[";
+$endplot = "]";
+
+###########################################
+###########################################
+# Begin VectorField3D
+
+sub VectorField3D {
+
+###########################################
+ #
+ # Set default options
+ #
+
+ my %options = (
+ Fx => Formula("1"),
+ Fy => Formula("1"),
+ Fz => Formula("1"),
+ xvar => 'x',
+ yvar => 'y',
+ zvar => 'z',
+ xmin => -3,
+ xmax => 3,
+ ymin => -3,
+ ymax => 3,
+ zmin => -3,
+ zmax => 3,
+ xsamples => 20,
+ ysamples => 20,
+ zsamples => 20,
+ axesframed => 1,
+ xaxislabel => "X",
+ yaxislabel => "Y",
+ zaxislabel => "Z",
+ vectorcolor => "RGBColor[0.0,0.0,1.0]",
+ vectorscale => 0.2,
+ vectorthickness => 0.001,
+ xavoid => 1000000,
+ yavoid => 1000000,
+ zavoid => 1000000,
+ outputtype => 4,
+ @_
+ );
+
+ my $Fxsubroutine;
+ my $Fysubroutine;
+ my $Fzsubroutine;
+
+ $options{Fx}->perlFunction('Fxsubroutine', [ "$options{xvar}", "$options{yvar}", "$options{zvar}" ]);
+ $options{Fy}->perlFunction('Fysubroutine', [ "$options{xvar}", "$options{yvar}", "$options{zvar}" ]);
+ $options{Fz}->perlFunction('Fzsubroutine', [ "$options{xvar}", "$options{yvar}", "$options{zvar}" ]);
+
+######################################################
+ #
+ # Generate plot data
+ #
+
+ my $dx = ($options{xmax} - $options{xmin}) / $options{xsamples};
+ my $dy = ($options{ymax} - $options{ymin}) / $options{ysamples};
+ my $dz = ($options{zmax} - $options{zmin}) / $options{zsamples};
+
+ my $xtail;
+ my $ytail;
+ my $ztail;
+
+ foreach my $i (0 .. $options{xsamples}) {
+ $xtail[$i] = $options{xmin} + $i * $dx;
+ foreach my $j (0 .. $options{ysamples}) {
+ $ytail[$j] = $options{ymin} + $j * $dy;
+ foreach my $k (0 .. $options{zsamples}) {
+ $ztail[$k] = $options{zmin} + $k * $dz;
+
+ if ($xtail[$i] == $options{xavoid} && $ytail[$j] == $options{yavoid} && $ztail[$k] == $options{zavoid})
+ {
+
+ $FX[$i][$j][$k] = 0;
+ $FY[$i][$j][$k] = 0;
+ $FZ[$i][$j][$k] = 0;
+
+ } else {
+
+ $FX[$i][$j][$k] = sprintf("%.3f",
+ $options{vectorscale} * (Fxsubroutine($xtail[$i], $ytail[$j], $ztail[$k])->value));
+ $FY[$i][$j][$k] = sprintf("%.3f",
+ $options{vectorscale} * (Fysubroutine($xtail[$i], $ytail[$j], $ztail[$k])->value));
+ $FZ[$i][$j][$k] = sprintf("%.3f",
+ $options{vectorscale} * (Fzsubroutine($xtail[$i], $ytail[$j], $ztail[$k])->value));
+
+ }
+
+ $xtail[$i] = sprintf("%.3f", $xtail[$i]);
+ $ytail[$j] = sprintf("%.3f", $ytail[$j]);
+ $ztail[$k] = sprintf("%.3f", $ztail[$k]);
+
+ $xtip[$i][$j][$k] = $xtail[$i] + sprintf("%.3f", $FX[$i][$j][$k]);
+ $ytip[$i][$j][$k] = $ytail[$j] + sprintf("%.3f", $FY[$i][$j][$k]);
+ $ztip[$i][$j][$k] = $ztail[$k] + sprintf("%.3f", $FZ[$i][$j][$k]);
+
+ $xleftbarb[$i][$j][$k] = sprintf("%.3f", $xtail[$i] + 0.8 * $FX[$i][$j][$k] - 0.2 * $FY[$i][$j][$k]);
+ $yleftbarb[$i][$j][$k] = sprintf("%.3f", $ytail[$j] + 0.8 * $FY[$i][$j][$k] + 0.2 * $FX[$i][$j][$k]);
+
+ $xrightbarb[$i][$j][$k] = sprintf("%.3f", $xtail[$i] + 0.8 * $FX[$i][$j][$k] + 0.2 * $FY[$i][$j][$k]);
+ $yrightbarb[$i][$j][$k] = sprintf("%.3f", $ytail[$j] + 0.8 * $FY[$i][$j][$k] - 0.2 * $FX[$i][$j][$k]);
+
+ $zbarb[$i][$j][$k] = sprintf("%.3f", $ztail[$k] + 0.8 * $FZ[$i][$j][$k]);
+
+ }
+ }
+ }
+
+###########################################################################
+ #
+ # Generate plotstructure from the plotdata.
+ #
+ # The plotstucture is a list of arrows (made of lines) that
+ # LiveGraphics3D reads as input.
+ #
+ # For more information on the format of the plotstructure, see
+ # http://www.math.umn.edu/~rogness/lg3d/page_NoMathematica.html
+ # http://www.vis.uni-stuttgart.de/~kraus/LiveGraphics3D/documentation.html
+ #
+###########################################
+ #
+ # Generate the polygons in the plotstructure
+ #
+
+ my $plotstructure = "{{{{$options{vectorcolor},Thickness[$options{vectorthickness}],";
+
+ foreach my $i (0 .. $options{xsamples}) {
+ foreach my $j (0 .. $options{ysamples}) {
+ foreach my $k (0 .. $options{zsamples}) {
+
+ $plotstructure =
+ $plotstructure
+ . "Line[{"
+ . "{$xtail[$i],$ytail[$j],$ztail[$k]},"
+ . "{$xtip[$i][$j][$k],$ytip[$i][$j][$k],$ztip[$i][$j][$k]}" . "}],"
+ . "Line[{"
+ . "{$xleftbarb[$i][$j][$k],$yleftbarb[$i][$j][$k],$zbarb[$i][$j][$k]},"
+ . "{$xtip[$i][$j][$k],$ytip[$i][$j][$k],$ztip[$i][$j][$k]},"
+ . "{$xrightbarb[$i][$j][$k],$yrightbarb[$i][$j][$k],$zbarb[$i][$j][$k]}" . "}]";
+
+ if (($i < $options{xsamples}) || ($j < $options{ysamples}) || ($k < $options{zsamples})) {
+ $plotstructure = $plotstructure . ",";
+ }
+ }
+ }
+ }
+
+ $plotstructure = $plotstructure . "}}}}";
+
+##############################################
+ #
+ # Add plot options to the plotoptions string
+ #
+
+ my $plotoptions = "";
+
+ if (($options{outputtype} > 1) || ($options{axesframed} == 1)) {
+ $plotoptions =
+ $plotoptions
+ . "Axes->True,AxesLabel->"
+ . "{$options{xaxislabel},$options{yaxislabel},$options{zaxislabel}}";
+ }
+
+####################################################
+ #
+ # Return only the plotstring (if outputtype=>1),
+ # or only plotoptions (if outputtype=>2),
+ # or plotstring, plotoptions (if outputtype=>2),
+ # or the entire plot (default) (if outputtype=>4)
+
+ if ($options{outputtype} == 1) {
+ return $plotstructure;
+ } elsif ($options{outputtype} == 2) {
+ return $plotoptions;
+ } elsif ($options{outputtype} == 3) {
+ return "{" . $plotstructure . "," . $plotoptions . "}";
+ } elsif ($options{outputtype} == 4) {
+ return $beginplot . $plotstructure . "," . $plotoptions . $endplot;
+ } else {
+ return "Invalid outputtype (outputtype should be a number 1 through 4).";
+ }
+
+} # End VectorField3D
+##############################################
+##############################################
+
+1;
diff --git a/macros/graph/PCCgraphMacros.pl b/macros/graph/PCCgraphMacros.pl
new file mode 100644
index 0000000000..6bb10719d0
--- /dev/null
+++ b/macros/graph/PCCgraphMacros.pl
@@ -0,0 +1,128 @@
+
+=head1 NAME
+
+PCCgraphMacros.pl - A collection of tools found to be useful for graphs during the development of the PCC problem library.
+
+=head1 DESCRIPTION
+
+This file implements graphing subroutines developed for use in problems originally developed for a PCC problem library
+
+To use it, load the macro file:
+
+ loadMacros(
+ "PGstandard.pl",
+ "MathObjects.pl",
+ "PCCgraphMacros.pl",
+ );
+
+
+=cut
+
+sub _init_PCCgraphMacros { loadMacros('PCCmacros.pl'); }
+
+###############################
+#Some standard values
+################################
+sub xPixels { 240; } #number of hor pixels in the graph object
+
+sub yPixels { 240; } #number of ver pixels in the graph object
+
+sub xScreen { 240; } #number of hor pixels used on screen
+
+sub yScreen { 240; } #number of ver pixels used on screen
+
+sub TeXscalar { 400; } #400=>image is at 40.0% in hardcopy
+
+sub EnlargeImageStatementPGML {
+ KeyboardInstructions(
+ 'If you would like to enlarge the graph or make numbers and letters easier to read, you may click on it to open it in a new window. You may then enlarge that window with your mouse. To further enlarge the image, use your browser\'s zoom capabilities. On a PC this is usually *ctrl shift +* (and zooming out is *ctrl -*). On an Apple computer this is usually *apple shift +* (and zooming out is *apple -*).'
+ );
+}
+
+sub EnlargeImageStatement {
+ KeyboardInstructions(
+ 'If you would like to enlarge the graph or make numbers and letters easier to read, you may click on it to open it in a new window. You may then enlarge that window with your mouse. To further enlarge the image, use your browser\'s zoom capabilities. On a PC this is usually $BBOLD ctrl shift +$EBOLD (and zooming out is $BBOLD ctrl -$EBOLD). On an Apple computer this is usually $BBOLD apple shift +$EBOLD (and zooming out is $BBOLD apple -$EBOLD).'
+ );
+}
+
+###############################
+#Name: NiceGraphParameters
+#Input: References to two arrays: one with all the x "action" in a graph, the other with the y "action"
+#Optional Input: centerOrigin, centerXaxis, centerYaxis, buffer, roughTickNum, roughTickNumX, roughTickNumY
+#Output: A reference to an array (xmin, xmax, ymin, ymax, xticknumber, yticknumber). These parameters will make a nice looking scale for a graph that captures all of the x and y data.
+################################
+sub NiceGraphParameters {
+ my $xact = shift;
+ my $yact = shift;
+ my %options = @_;
+ my @xaction = @{$xact};
+ my @yaction = @{$yact};
+
+ if ($options{centerYaxis} or $options{centerOrigin}) {
+ my @temp = ();
+ for my $i (@xaction) { push(@temp, -$i); }
+ @xaction = (@xaction, @temp);
+ }
+
+ if ($options{centerXaxis} or $options{centerOrigin}) {
+ my @temp = ();
+ for my $i (@yaction) { push(@temp, -$i); }
+ @yaction = (@yaction, @temp);
+ }
+
+ my (@max, @min);
+ $max[0] = max(@xaction);
+ $min[0] = min(@xaction);
+ $max[1] = max(@yaction);
+ $min[1] = min(@yaction);
+
+ my @roughTickNum;
+ $roughTickNum[0] = 10;
+ $roughTickNum[1] = 10;
+ for my $i (0, 1) { $roughTickNum[$i] = $options{roughTickNum} if (defined $options{roughTickNum}); }
+ $roughTickNum[0] = $options{roughTickNumX} if (defined $options{roughTickNumX});
+ $roughTickNum[1] = $options{roughTickNumY} if (defined $options{roughTickNumY});
+
+ my $buffer = 1.618;
+ $buffer = $options{buffer} if (defined $options{buffer});
+
+ @pixels = (xPixels(), yPixels());
+
+ my (@center, @left, @right, @tickexp, %tickcoefs, @tickcoef, @marksep, @ticknum, @scale, @adjRoughTickNum);
+ for my $i (0, 1) {
+ $center[$i] = ($max[$i] + $min[$i]) / 2;
+ $low[$i] = $center[$i] + $buffer * ($min[$i] - $center[$i]);
+ $high[$i] = $center[$i] + $buffer * ($max[$i] - $center[$i]);
+ $tickexp[$i] = round(log(($high[$i] - $low[$i]) / $roughTickNum[$i]) / log(10));
+ if ($i == 0) { $adjRoughTickNum[0] = min($roughTickNum[0], $pixels[0] / ((abs($tickexp[0]) + 3) * 10)); }
+ if ($i == 1) { $adjRoughTickNum[1] = min($roughTickNum[1], $pixels[1] / ((abs($tickexp[1]) + 1) * 10)); }
+ %tickcoefs = ();
+ %tickcoefs = (
+ .2 => abs(.2 * 10**$tickexp[$i] - ($high[$i] - $low[$i]) / $adjRoughTickNum[$i]),
+ .5 => abs(.5 * 10**$tickexp[$i] - ($high[$i] - $low[$i]) / $adjRoughTickNum[$i]),
+ 1 => abs(1 * 10**$tickexp[$i] - ($high[$i] - $low[$i]) / $adjRoughTickNum[$i]),
+ 2 => abs(2 * 10**$tickexp[$i] - ($high[$i] - $low[$i]) / $adjRoughTickNum[$i]),
+ 5 => abs(5 * 10**$tickexp[$i] - ($high[$i] - $low[$i]) / $adjRoughTickNum[$i])
+ );
+ $tickcoef[$i] = .2;
+
+ for my $key (.2, .5, 1, 2, 5)
+ # division by 1.7 is a hack to make ticks more frequent
+ # this may need redoing if problems are reported
+ { $tickcoef[$i] = $key if ($tickcoefs{$key} < $tickcoefs{ $tickcoef[$i] } / 1.7); }
+ $marksep[$i] = 10**($tickexp[$i]) * $tickcoef[$i];
+ $ticknum[$i] = 2 * ceil(($high[$i] - $low[$i]) / 2 / $marksep[$i]);
+ $max[$i] = ceil($high[$i] / $marksep[$i]) * $marksep[$i];
+ $min[$i] = $max[$i] - $marksep[$i] * $ticknum[$i];
+ @temp = ();
+ }
+
+ my @xticks =
+ map { $marksep[0] * 2 * $_ } (int($min[0] / $marksep[0] / 2 + 0.1)) .. (int($max[0] / $marksep[0] / 2 - 0.1));
+ my @yticks =
+ map { $marksep[1] * 2 * $_ } (int($min[1] / $marksep[1] / 2 + 0.1)) .. (int($max[1] / $marksep[1] / 2 - 0.1));
+
+ return ($min[0], $max[0], $min[1], $max[1], $ticknum[0], $ticknum[1], \@xticks, \@yticks, $marksep[0], $marksep[1]);
+}
+
+1;
diff --git a/macros/PGanalyzeGraph.pl b/macros/graph/PGanalyzeGraph.pl
similarity index 100%
rename from macros/PGanalyzeGraph.pl
rename to macros/graph/PGanalyzeGraph.pl
diff --git a/macros/graph/PGgraphgrid.pl b/macros/graph/PGgraphgrid.pl
new file mode 100644
index 0000000000..d24c787bab
--- /dev/null
+++ b/macros/graph/PGgraphgrid.pl
@@ -0,0 +1,130 @@
+sub _PGgraphgrid_init { }; # don't reload this file
+
+=pod
+
+=head1 NAME
+
+PGgraphgrid.pl
+
+=head1 SYNOPSIS
+
+Provides subroutines plotting a parallelogram grid on a
+graph object created by PGgraphmacros.pl.
+
+=head1 DESCRIPTION
+
+Load the C macro file.
+
+=over 12
+
+=item C
+
+= back
+
+The macro C provides two different subroutines
+for graphing grids: the first one draws the grid using parallel lines
+and should be fast, and a second that draws each individual
+parallelogram and should be slow (but will suffer less from clipping):
+
+=over 12
+
+=item C
+
+=item C
+
+=back
+
+Here C<$gr> is a graph object created by C,
+C<$B> is a two by two MathObjects matrix, the integers C<$hmin> and
+C<$vmin> specify one corner of the grid, the integers C<$hmax> and
+C<$vmax> specify the opposite corner of the grid, C<$color> is a string
+that specifies the drawing color, and C<$linewidth> is a positive integer
+for the line width.
+
+=over 12
+
+=head1 AUTHORS
+
+Paul Pearson, Hope College, Department of Mathematics
+
+=cut
+
+################################################
+
+loadMacros("MathObjects.pl", "PGgraphmacros.pl",);
+
+sub graphgrid1 {
+ # inputs
+ my $gr = shift; # graph object
+ my $B = shift; # 2x2 basis matrix with B = (b1 | b2), with basis vectors in columns
+ my $xmin = shift;
+ my $ymin = shift;
+ my $xmax = shift;
+ my $ymax = shift; # (xmin,ymin) and (xmax,ymax) are two of the corners of the grid (must be integers)
+ my $color = shift; # grid color
+ my $linewidth = shift; # line thickness
+
+ # standard coordinates for horizontal lines
+ my @hx = (); # horizontal lines, x values
+ my @hy = (); # horizontal lines, y values
+ foreach my $y ($ymin .. $ymax) {
+ push(@hx, ($xmin, $xmax));
+ push(@hy, ($y, $y));
+ }
+ my $H = Matrix([ [@hx], [@hy] ]);
+ my $BH = $B * $H;
+ my @bh = $BH->value;
+ # draw parallelogram grid on graph object
+ # "horizontal" lines
+ my $bh_ncol = ($BH->dimensions)[1];
+ foreach my $i (0 .. $bh_ncol / 2) {
+ $gr->moveTo($bh[0][ 2 * $i ], $bh[1][ 2 * $i ]);
+ $gr->lineTo($bh[0][ 2 * $i + 1 ], $bh[1][ 2 * $i + 1 ], $color, $linewidth);
+ }
+
+ # standard coordinates for vertical lines
+ my @vx = (); # vertical lines, x values
+ my @vy = (); # vertical lines, y values
+ foreach my $x ($xmin .. $xmax) {
+ push(@vx, ($x, $x));
+ push(@vy, ($ymin, $ymax));
+ }
+ my $V = Matrix([ [@vx], [@vy] ]);
+ my $BV = $B * $V;
+ my @bv = $BV->value;
+ # draw parallelogram grid on graph object
+ # "vertical" lines
+ my $bv_ncol = ($BV->dimensions)[1];
+ foreach my $i (0 .. $bv_ncol / 2) {
+ $gr->moveTo($bv[0][ 2 * $i ], $bv[1][ 2 * $i ]);
+ $gr->lineTo($bv[0][ 2 * $i + 1 ], $bv[1][ 2 * $i + 1 ], $color, $linewidth);
+ }
+
+} # end graphgrid1
+
+sub graphgrid2 {
+ # inputs
+ my $gr = shift; # graph object
+ my $B = shift; # 2x2 basis matrix with B = (b1 | b2), with basis vectors in columns
+ my $xmin = shift;
+ my $ymin = shift;
+ my $xmax = shift;
+ my $ymax = shift; # (xmin,ymin) and (xmax,ymax) are two of the corners of the grid (must be integers)
+ my $color = shift; # grid color
+ my $linewidth = shift; # line thickness
+
+ foreach my $x ($xmin .. $xmax) {
+ foreach my $y ($ymin .. $ymax) {
+ my $X = Matrix([ [ $x, $x + 1, $x + 1, $x, $x ], [ $y, $y, $y + 1, $y + 1, $y ] ]);
+ my $BX = $B * $X;
+ my @bx = $BX->value;
+ $gr->moveTo($bx[0][0], $bx[1][0]);
+ $gr->lineTo($bx[0][1], $bx[1][1], $color, $linewidth);
+ $gr->lineTo($bx[0][2], $bx[1][2], $color, $linewidth);
+ $gr->lineTo($bx[0][3], $bx[1][3], $color, $linewidth);
+ $gr->lineTo($bx[0][4], $bx[1][4], $color, $linewidth);
+ }
+ }
+} # end graphgrid2
+
+1;
diff --git a/macros/PGgraphmacros.pl b/macros/graph/PGgraphmacros.pl
similarity index 100%
rename from macros/PGgraphmacros.pl
rename to macros/graph/PGgraphmacros.pl
diff --git a/macros/graph/PGnauGraphics.pl b/macros/graph/PGnauGraphics.pl
new file mode 100644
index 0000000000..728ba25950
--- /dev/null
+++ b/macros/graph/PGnauGraphics.pl
@@ -0,0 +1,328 @@
+
+sub _PGnauGraphics_init { }; #don't reload this file
+# loadMacros('PGunion.pl');
+loadMacros('PGgraphmacros.pl', 'unionTables.pl', 'PGchoicemacros.pl');
+
+######################################
+#Name: Plot
+#Input: Graphical object
+#Optional Input: tex_size => n: control size of tex output. n = 10*percentage of page (i.e. 200 = 20% of page)
+# note that in single column, the images are twice as big, so 200 = 40% of page
+# single => 0,1: turn on or off single column display which makes pictures fit (default = 0)
+#Output: An image with appropriate width and height of the graphical object
+######################################
+sub Plot {
+ my ($pic, @options) = @_;
+ my ($tex, $val, $plot, @size, $width, $height);
+
+ while (scalar @options > 0) {
+ $val = shift @options;
+ if ($val eq 'single') {
+ if (shift @options == 1) { $tex = 200 }
+ } elsif ($val eq 'tex_size') {
+ $tex = shift @options;
+ } else {
+ shift @options;
+ }
+ }
+
+ $tex = 400 unless defined($tex);
+ ($width, $height) = @{ $pic->size() };
+
+ $plot =
+ image(insertGraph($pic), width => $width, height => $height, tex_size => $tex, extra_html_tags => 'border=0');
+}
+
+######################################
+#Name: checkbox_table
+#Input: [values list], [answer list i.e. 1,0,1,0]
+#Optional Input: border => n - width of border in table (default = 0)
+# tex_size => n - control size of tex output for pictures (defualt = 10 * 95 / # of columns)
+# geometry =>[r,c] - the number of rows and columns desired in the table (default = 2,2)
+# labels => [@list] - a list of labels that appear beside the checkboxes. (default is blank)
+# [..] - other answer lists, included as many as desired. Note the lists must use the same
+# values as the others and answer lists must be in the same basic order.
+#Output: A table to be displayed and the answer evaluator.
+# The table should be placed between BEGIN_TEXT and END_TEXT.
+# The evaluator should be placed after END_TEXT by itself.
+######################################
+sub checkbox_table {
+ my ($val_ref, $ans_ref, @options) = @_;
+
+ my (
+ $i, $j, $k, @ans, $lab, $cols, $name, @vals,
+ $rows, @rule, $size, $true, $val1, $val2, $val3, $count,
+ @labels, @names, @order, @other, $quest, $table, $total, $answer,
+ $length, $letter, $tex_size, @ansrow, @valrow, @answer, %row_opts
+ );
+
+ @vals = @{$val_ref};
+ @ans0 = @{$ans_ref};
+ $count = 1;
+
+ while (@options) {
+ $val1 = shift @options;
+ if ($val1 eq 'border') {
+ $border = shift @options;
+ } elsif ($val1 eq 'tex_size') {
+ $single = shift @options;
+ } elsif ($val1 eq 'geometry') {
+ $val1 = shift @options;
+ ($rows, $cols) = @{$val1};
+ } elsif ($val1 eq 'labels') {
+ $val1 = shift @options;
+ @labels = @{$val1};
+ } else {
+ $val2 = "ans$count";
+ @$val2 = @{$val1};
+ $count++;
+ }
+ }
+
+ $border = 0 unless defined($border);
+ $rows = 2 unless defined($rows);
+ $cols = 2 unless defined($cols);
+ $tex_size = int(95 / $cols) * 10 unless defined($tex_size);
+
+ $size = scalar @vals;
+ $val1 = int(($size - 1) / $cols) + 1;
+ if ($val1 != $rows) { return ("Incorrect geometry or input.", 0); }
+ for ($i = 0; $i < $count; $i++) {
+ $val = "ans$i";
+ if (scalar @$val != $size) { return ("Values and answers do not match.", 0); }
+ }
+
+ $length = scalar @labels;
+ $total = $count * $size;
+ if ($total == $size) {
+ for ($i = $length; $i < $total; $i++) {
+ push @labels, '';
+ }
+ } else {
+ while ($length < $total) {
+ $loc = int($length / $size) + 1;
+ push @labels, "$loc";
+ $length++;
+ }
+ }
+
+ @order = NchooseK($size, $size);
+ @vals = @vals[@order];
+ for ($i = 0; $i < $count; $i++) {
+ $val1 = "ans$i";
+ $val2 = "labels$i";
+ $j = $i * $size;
+ $k = $j + $size - 1;
+ @$val1 = @$val1[@order];
+ @$val2 = @labels[ $j .. $k ];
+ @$val2 = @$val2[@order];
+ }
+
+ for ($i = 0; $i < $size; $i++) {
+ if ($vals[$i] =~ /WWPlot=HASH/) {
+ $vals[$i] = Plot($vals[$i], tex_size => $tex_size);
+ }
+ }
+
+ for ($i = 0; $i < $count; $i++) {
+ $true = '';
+ $val1 = "ans$i";
+ $val2 = "labels$i";
+ $val3 = "rule$i";
+ $name = "CheckBoxName$i";
+ for ($j = 0; $j < $size; $j++) {
+ $letter = $ALPHABET[$j];
+ if ($$val2[$j] eq '') {
+ $lab = $letter;
+ } else {
+ $lab = "$letter: " . "$$val2[$j]";
+ }
+ $$val3[$j] =
+ ($j ? NAMED_ANS_CHECKBOX_OPTION($name, $letter, $lab) : NAMED_ANS_CHECKBOX($name, $letter, $lab));
+ # TeX-mode uses \item, but we're not in a list, so remove it
+ # and put a fake box in front
+ $$val3[$j] =~ s/\\item/\$\\Box\$ / if $displayMode eq 'TeX';
+ $true .= $letter if $$val1[$j] == 1;
+ }
+ push @answer, LABELED_ANS($name => checkbox_cmp($true));
+ }
+
+ $table = BeginTable(spacing => 5, border => $border, center => 0);
+ %row_opts = (separation => 0);
+ for ($i = 0; $i < $rows; $i++) {
+ $j = 0;
+ @valrow = ();
+ while (@vals && $j < $cols) {
+ push @valrow, shift @vals;
+ $j++;
+ }
+ $table .= AlignedRow([@valrow], %row_opts);
+ for ($j = 0; $j < $count + 1; $j++) {
+ @ansrow = ();
+ $val1 = "rule$j";
+ $k = 0;
+ while (@$val1 && $k < $cols) {
+ push @ansrow, shift @$val1;
+ $k++;
+ }
+ $table .= AlignedRow([@ansrow], %row_opts);
+ }
+ if ($rows > 1 && $i > 0) {
+ $table .= TableSpace(16, 8);
+ }
+ }
+ $table .= EndTable();
+ unshift @answer, ($table);
+
+ return @answer;
+}
+
+######################################
+#Name: radio_table
+#Input: [values list], [answer list i.e. 1,0,1,0]
+#Optional Input: border => n - width of border in table (default = 0)
+# tex_size => n - control size of tex output for pictures (defualt = 10 * 95 / # of columns)
+# geometry =>[r,c] - the number of rows and columns desired in the table (default = 2,2)
+# labels => [@list] - a list of labels that appear beside the radio buttons. (default is blank)
+# [..] - other answer lists, included as many as desired. Note the lists must use the same
+# values as the others and answer lists must be in the same basic order.
+#Output: A table to be displayed and the answer evaluator.
+# The table should be placed between BEGIN_TEXT and END_TEXT.
+# The evaluator should be placed after END_TEXT by itself.
+######################################
+sub radio_table {
+ my ($val_ref, $ans_ref, @options) = @_;
+
+ my (
+ $i, $j, $k, @ans, $lab, $cols, $name, @vals,
+ $rows, @rule, $size, $true, $val1, $val2, $val3, $count,
+ @labels, @names, @order, @other, $quest, $table, $total, $answer,
+ $length, $letter, $tex_size, @ansrow, @valrow, @answer, %row_opts
+ );
+
+ @vals = @{$val_ref};
+ @ans0 = @{$ans_ref};
+ $count = 1;
+
+ while (@options) {
+ $val1 = shift @options;
+ if ($val1 eq 'border') {
+ $border = shift @options;
+ } elsif ($val1 eq 'tex_size') {
+ $tex_size = shift @options;
+ } elsif ($val1 eq 'geometry') {
+ $val1 = shift @options;
+ ($rows, $cols) = @{$val1};
+ } elsif ($val1 eq 'labels') {
+ $val1 = shift @options;
+ @labels = @{$val1};
+ } else {
+ $val2 = "ans$count";
+ @$val2 = @{$val1};
+ $count++;
+ }
+ }
+
+ $border = 0 unless defined($border);
+ $rows = 2 unless defined($rows);
+ $cols = 2 unless defined($cols);
+ $tex_size = int(95 / $cols) * 10 unless defined($tex_size);
+
+ $size = scalar @vals;
+ $val1 = int(($size - 1) / $cols) + 1;
+ if ($val1 != $rows) { return ("Incorrect geometry or input.", 0); }
+ for ($i = 0; $i < $count; $i++) {
+ $val1 = "ans$i";
+ if (scalar @$val1 != $size) { return ("Values and answers do not match.", 0); }
+ $total = 0;
+ for ($j = 0; $j < $size; $j++) {
+ $total += $$val1[$j];
+ }
+ if ($total != 1) { return ("Wrong number of correct answers for radio button.", 0); }
+ }
+
+ $length = scalar @labels;
+ $total = $count * $size;
+ if ($total == $size) {
+ for ($i = $length; $i < $total; $i++) {
+ push @labels, '';
+ }
+ } else {
+ while ($length < $total) {
+ $loc = int($length / $size) + 1;
+ push @labels, "$loc";
+ $length++;
+ }
+ }
+
+ @order = NchooseK($size, $size);
+ @vals = @vals[@order];
+ for ($i = 0; $i < $count; $i++) {
+ $val1 = "ans$i";
+ $val2 = "labels$i";
+ $j = $i * $size;
+ $k = $j + $size - 1;
+ @$val1 = @$val1[@order];
+ @$val2 = @labels[ $j .. $k ];
+ @$val2 = @$val2[@order];
+ }
+
+ for ($i = 0; $i < $size; $i++) {
+ if ($vals[$i] =~ /WWPlot=HASH/) {
+ $vals[$i] = Plot($vals[$i], tex_size => $tex_size);
+ }
+ }
+
+ for ($i = 0; $i < $count; $i++) {
+ $true = '';
+ $val1 = "ans$i";
+ $val2 = "labels$i";
+ $val3 = "rule$i";
+ $name = "CheckBoxName$i";
+ for ($j = 0; $j < $size; $j++) {
+ $letter = $ALPHABET[$j];
+ if ($$val2[$j] eq '') {
+ $lab = $letter;
+ } else {
+ $lab = "$letter: " . "$$val2[$j]";
+ }
+ $$val3[$j] = ($j ? NAMED_ANS_RADIO_EXTENSION($name, $letter, $lab) : NAMED_ANS_RADIO($name, $letter, $lab));
+ # TeX-mode uses \item, but we're not in a list, so remove it
+ # and put a fake box in front
+ $$val3[$j] =~ s/\\item/\$\\Box\$ / if $displayMode eq 'TeX';
+ $true .= $letter if $$val1[$j] == 1;
+ }
+ push @answer, LABELED_ANS($name => checkbox_cmp($true));
+ }
+
+ $table = BeginTable(spacing => 5, border => $border, center => 0);
+ %row_opts = (separation => 0);
+ for ($i = 0; $i < $rows; $i++) {
+ $j = 0;
+ @valrow = ();
+ while (@vals && $j < $cols) {
+ push @valrow, shift @vals;
+ $j++;
+ }
+ $table .= AlignedRow([@valrow], %row_opts);
+ for ($j = 0; $j < $count + 1; $j++) {
+ @ansrow = ();
+ $val1 = "rule$j";
+ $k = 0;
+ while (@$val1 && $k < $cols) {
+ push @ansrow, shift @$val1;
+ $k++;
+ }
+ $table .= AlignedRow([@ansrow], %row_opts);
+ }
+ if ($rows > 1 && $i > 0) {
+ $table .= TableSpace(16, 8);
+ }
+ }
+ $table .= EndTable();
+ unshift @answer, ($table);
+
+ return @answer;
+}
+
+1;
diff --git a/macros/PGstatisticsGraphMacros.pl b/macros/graph/PGstatisticsGraphMacros.pl
similarity index 100%
rename from macros/PGstatisticsGraphMacros.pl
rename to macros/graph/PGstatisticsGraphMacros.pl
diff --git a/macros/PGtikz.pl b/macros/graph/PGtikz.pl
similarity index 100%
rename from macros/PGtikz.pl
rename to macros/graph/PGtikz.pl
diff --git a/macros/graph/imageChoice.pl b/macros/graph/imageChoice.pl
new file mode 100644
index 0000000000..4695e6dffc
--- /dev/null
+++ b/macros/graph/imageChoice.pl
@@ -0,0 +1,104 @@
+loadMacros('PGchoicemacros.pl', 'unionUtils.pl', 'choiceUtils.pl',);
+
+sub _imageChoice_init { }; # don't reload this file
+
+######################################################################
+#
+# Create a match list where the answers are images
+#
+# Usage: $ml = new_image_match_list(options);
+#
+# where options are those that can be supplied to image_print_a below.
+# The answers should be an image name or reference to to a plot object
+# (or a reference to a pair of either of these), and they are passed
+# to the Image function for processing. See unionUtils.pl for more
+# information on how these are handled.
+#
+sub new_image_match_list {
+ my $ml = new Match(random(1, 2000, 1), \&alt_print_q, \&img_print_a);
+ $ml->{ImageOptions} = [@_];
+ $ml;
+}
+
+######################################################################
+#
+# A print routine for image matching. This is designed to display
+# four graphs per row. More can be included by setting the ImageOptions
+# variable in the match list. For example:
+#
+# $ml->{ImageOptions} = [size => [100,100]]
+#
+# You can include the following options:
+#
+# size => [w,h] the width and height of the images
+# width => n the width of the images (obsolete)
+# height => n the height of the images (obsolete)
+# tex_size => n the size for the TeX version of the image
+# separation => n the spacing between the images in a row
+# vseparation => n the spacing between the images and captions
+# link => 0 or 1 1 to make a link to the original image
+# columns => n the number of images in each row (defaults to 4)
+# border => n the width of the image border
+#
+
+sub img_print_a {
+ my $self = shift;
+ $self->{ImageOptions} = [] unless defined($self->{ImageOptions});
+ my %options = (
+ width => 150,
+ height => 150,
+ tex_size => 200,
+ columns => 4,
+ separation => 15,
+ vseparation => 5,
+ link => 0,
+ @{ $self->{ImageOptions} }
+ );
+ my ($w, $h, $m) = ($options{width}, $options{height}, $options{columns});
+ ($w, $h) = @{ $options{size} } if defined($options{size});
+ my ($sep, $vsep) = ($options{separation}, $options{vseparation});
+ my ($tsize, $link) = ($options{tex_size}, $options{link});
+ my $border = $options{border};
+ $border = ($link ? 2 : 1) unless defined($border);
+ my $HTML;
+ my @images = ();
+ my @labels = ();
+ my $i = 0;
+ my $out;
+
+ $out = BeginTable(tex_spacing => "5pt", tex_border => "5pt");
+ while ($image = shift) {
+ push(
+ @images,
+ Image(
+ $image,
+ size => [ $w, $h ],
+ tex_size => $tsize,
+ link => $link,
+ border => $border
+ )
+ );
+ push(
+ @labels,
+ MODES(
+ TeX => "\\hfil \\textbf{$main::ALPHABET[$i]}",
+ Latex2HTML => $bHTML . "$main::ALPHABET[$i] " . $eHTML,
+ HTML => "$main::ALPHABET[$i] "
+ )
+ );
+ if ((++$i % $m) == 0) {
+ $out .= TableSpace(2 * $vsep, 6) if ($i > $m);
+ $out .= Row([@images], separation => $sep) . TableSpace($vsep, 0) . Row([@labels], separation => $sep);
+ @images = ();
+ @labels = ();
+ }
+ }
+ if (scalar(@images) > 0) {
+ $out .= TableSpace(2 * $vsep) if ($i > $m && $displayMode ne "TeX");
+ $out .= Row([@images], separation => $sep) . TableSpace($vsep, 0) . Row([@labels], separation => $sep);
+ }
+ $out .= EndTable(tex_border => "5pt");
+ $out;
+}
+
+1;
diff --git a/doc/MathObjects/macros/unionImage.pl b/macros/graph/unionImage.pl
similarity index 82%
rename from doc/MathObjects/macros/unionImage.pl
rename to macros/graph/unionImage.pl
index c02b26fd30..9dcc4e5b6a 100644
--- a/doc/MathObjects/macros/unionImage.pl
+++ b/macros/graph/unionImage.pl
@@ -1,3 +1,7 @@
+loadMacros("unionMacros.pl");
+
+sub _unionImage_init { }; # don't reload this file
+
######################################################################
#
# A routine to make including images easier to control
@@ -45,6 +49,7 @@
sub Image {
my $image = shift;
my $ilink;
+ return unless defined $image;
my %options = (
size => [ 150, 150 ],
tex_size => 200,
@@ -60,15 +65,23 @@ sub Image {
my $HTML;
my $TeX;
($image, $ilink) = @{$image} if (ref($image) eq "ARRAY");
+ $ilink = $ilink // '';
$image = alias(insertGraph($image)) if (ref($image) eq "WWPlot");
- $image = alias($image) unless ($image =~ m!^/!i);
+ $image = alias($image) unless ($image =~ m!^(/|https?:)!i); # see note
if ($ilink) {
$ilink = alias(insertGraph($ilink)) if (ref($ilink) eq "WWPlot");
- $ilink = alias($ilink) unless ($ilink =~ m!^/!i);
+ $ilink = alias($ilink) unless ($ilink =~ m!^(/|https?:)!i); # see note
} else {
$ilink = $image;
}
+ #
+ # Note: These cases were added to handle the examples where the
+ # $image tag has a full url -- in practice this arises when using lighttpd
+ # to server images from a different port
+ # e.g. http://hosted2.webwork.rochester.edu:8000/webwork2_course_files/....
+ # A smarter implementation of alias might make this check unnecessary
+ #
$border = (($link || $ilink ne $image) ? 2 : 1) unless defined($border);
$HTML =
' = 0) ? '+' : '-', sqrt(abs($co[2])));
+ my $ac = $co[0] * $co[2];
+ my @acbreakdown = ($a[0] * $b[1], $b[0] * $a[1]);
+
+ if (length($var[0]) == 2) {
+ $var[2] = substr $var[0], 0, 1;
+ $var[3] = substr $var[0], 1, 1;
+ $var[0] = 1;
+ } else {
+ $var[2] = 1;
+ $var[3] = 1;
+ }
+
+ my $expanded =
+ Formula("$co[0]*$var[0]^2*$var[2]^2*$var[3]^2 + $co[1]*$var[0]*$var[1]*$var[2]*$var[3] + $co[2]*$var[1]^2")
+ ->reduce;
+ my @factor = (
+ Formula("$a[0]*$var[0]*$var[2]*$var[3] + $b[0]*$var[1]")->reduce,
+ Formula("$a[1]*$var[0]*$var[2]*$var[3] + $b[1]*$var[1]")->reduce
+ );
+
+ my $factored = Formula("($factor[0])*($factor[1])")->reduce;
+ my $factoredSquare = "", $factoredSquareOutput = "";
+ if (($a[0] == $a[1]) && ($b[0] == $b[1])) {
+ $factoredSquare = Formula("($factor[0])**2")->reduce;
+ $factoredSquareOutput = '\newline &=' . $factoredSquare->TeX;
+ }
+
+ $outputString .=
+ 'We will factor \('
+ . $expanded->TeX
+ . '\) where \(a='
+ . $co[0]
+ . '\), \(b='
+ . $co[1]
+ . '\), and \(c='
+ . $co[2] . '\).'
+ . $PAR;
+
+ ### Attach an explanation if the quadratic is a perfect square
+ if ((($co[1])**2 - 4 * $co[0] * $co[2] == 0)
+ and ($co[0] > 0)
+ and ($co[2] > 0)
+ and ($sq[0] == int($sq[0]))
+ and ($sq[2] == int($sq[2])))
+ {
+ my @term = (
+ Formula("$sq[0]*$var[0]*$var[2]*$var[3]")->reduce,
+ Formula("$sq[0]*$var[0]*$var[2]*$var[3]")->reduce,
+ Formula("$sq[2]*$var[1]")->reduce,
+ Formula("$sq[2]*$var[1]")->reduce
+ );
+ $outputString .=
+ $BBOLD
+ . 'Perfect Square Formula: '
+ . $EBOLD
+ . 'The formula for perfect square trinomials is \[(a \pm b)^2=a^2 \pm 2ab + b^2\] Note that the polynomial \('
+ . $expanded->TeX
+ . '\) matches the formula' . "'"
+ . 's right side, and we have: \[\begin{aligned}[t]'
+ . $expanded->TeX
+ . '&=\left('
+ . $term[0]->TeX
+ . '\right)^2'
+ . $sq[1]
+ . '2\left('
+ . $term[1]->TeX
+ . '\right)\left('
+ . $term[2]->TeX
+ . '\right)+\left('
+ . $term[3]->TeX
+ . '\right)^2\newline &='
+ . $factored->TeX
+ . $factoredSquareOutput
+ . '\end{aligned}\]'
+ . $PAR;
+
+ }
+
+ ### Attach an explanation if the quadratic is a difference of squares
+ if (($co[1] == 0)
+ and ($co[0] > 0)
+ and ($co[2] < 0)
+ and ($sq[0] == int($sq[0]))
+ and ($sq[2] == int($sq[2])))
+ {
+ my @term = (Formula("$sq[0]*$var[0]*$var[2]*$var[3]")->reduce, Formula("$sq[2]*$var[1]")->reduce);
+ $outputString .=
+ $BBOLD
+ . 'Difference of Squares Formula: '
+ . $EBOLD
+ . 'The formula for a difference of squares is \[a^2-b^2 = (a+b)(a-b)\] Note that the polynomial \('
+ . $expanded->TeX
+ . '\) matches the formula' . "'"
+ . 's right side, and we have: \[\begin{aligned}[t]'
+ . $expanded->TeX
+ . '&=\left('
+ . $term[0]->TeX
+ . '\right)^2-\left('
+ . $term[1]->TeX
+ . '\right)^2\newline &='
+ . $factored->TeX
+ . '\end{aligned}\]'
+ . $PAR;
+
+ }
+
+ ### Attach an explanation if one can factor mentally.
+ if ($co[0] == 1) {
+ $outputString .=
+ $BBOLD
+ . 'Mental Method: '
+ . $EBOLD
+ . 'When we factor a polynomial in the form of \(x^2+bx+c\), \(x^2+bxy+cy^2\), or \(x^2y^2+bxy+c\) we can use mental math to factor. We need to find two numbers whose product is \(c\), and whose sum is \(b\).'
+ . $PAR
+ . 'For \('
+ . $expanded->TeX
+ . '\), since the product of \('
+ . $b[0]
+ . '\) and \('
+ . $b[1]
+ . '\) is \('
+ . $co[2]
+ . '\), and the sum of \('
+ . $b[0]
+ . '\) and \('
+ . $b[1]
+ . '\) is \('
+ . $co[1]
+ . '\), the factorization is \[\begin{aligned}[t]'
+ . $expanded->TeX . '&='
+ . $factored->TeX
+ . $factoredSquareOutput
+ . '\end{aligned}\]'
+ . $PAR;
+
+ }
+
+ ### Attach an explanation of the AC method.
+ my $sqac = int(sqrt(abs($ac)));
+ my @smallfactor;
+ my @largefactor;
+ for my $i (1 .. $sqac) {
+ my $j = $ac / $i;
+ if ($j == int($j)) { push(@smallfactor, $i, -$i); push(@largefactor, $j, -$j); }
+ }
+ my $factorPairsOutput = "";
+ for my $i (0 .. $#smallfactor) {
+ $factorPairsOutput .= $smallfactor[$i] . '(' . $largefactor[$i] . ')';
+ if ($i % 4 == 3) { $factorPairsOutput .= '\newline'; }
+ else { $factorPairsOutput .= '&'; }
+ }
+
+ my @term = (
+ Formula("$co[0]*$var[0]^2*$var[2]^2*$var[3]^2")->reduce,
+ Formula("$acbreakdown[0]*$var[0]*$var[1]*$var[2]*$var[3]")->reduce,
+ Formula("$acbreakdown[1]*$var[0]*$var[1]*$var[2]*$var[3]")->reduce,
+ Formula("$co[2]*$var[1]^2")->reduce,
+ Formula("$a[0]*$var[0]*$var[2]*$var[3]")->reduce,
+ Formula("$b[0]*$var[1]")->reduce,
+ );
+ $acStep1 = Compute("$term[0]+$term[1]+$term[2]+$term[3]")->reduce;
+ $acStep2 = Compute("$term[4]*($factor[1])+$term[5]*($factor[1])")->reduce;
+ $outputString .=
+ $BBOLD
+ . 'AC Method: '
+ . $EBOLD
+ . 'In this method, we first calculate the product of \(a\) and \(c\) in \(ax^2+bx+c\). For this problem, we have \(('
+ . $co[0]
+ . ') \cdot ('
+ . $co[2] . ') = '
+ . $ac . '\).'
+ . $PAR
+ . 'The number \('
+ . $ac
+ . '\) can be factored into the product of two numbers in the following ways: \[\begin{array}{lll}'
+ . $factorPairsOutput
+ . '\end{array}\] Looking at the corresponding sums, we want a sum of \('
+ . $co[1]
+ . '\), and this sum happens with \('
+ . (($acbreakdown[0] > 0) ? '' : '(')
+ . $acbreakdown[0]
+ . (($acbreakdown[0] > 0) ? '' : ')') . '+'
+ . (($acbreakdown[1] > 0) ? '' : '(')
+ . $acbreakdown[1]
+ . (($acbreakdown[1] > 0) ? '' : ')') . '='
+ . $co[1]
+ . '\) So we write \[\begin{aligned}[t]'
+ . $expanded->TeX . '&='
+ . $acStep1->TeX
+ . '\newline &='
+ . $acStep2->TeX
+ . '\newline &='
+ . $factored->TeX
+ . $factoredSquareOutput
+ . '\end{aligned}\]';
+
+ ### Explanation that uses rectangle diagrams
+ $outputString .=
+ $BBOLD
+ . 'Generic Rectangles Method: '
+ . $EBOLD
+ . 'First, we set up generic rectangles by putting the first term and third term in \('
+ . $expanded->TeX
+ . '\) into the top left and bottom right rectangles: '
+ . $PAR;
+
+ #### Make two pictures ###
+ my $xmin = 0; #The viewing window
+ my $xmax = 10;
+ my $ymin = 0;
+ my $ymax = 10;
+ @picture = ();
+ my $xStart = 2;
+ my $xEnd = 8;
+ my $yStart = 2;
+ my $yEnd = 8;
+ my @x = ($xStart, $xEnd);
+ my @y = ($yStart, $yEnd);
+ my $gap = $ymax / 100;
+
+ my ($box1, $box2, $box3, $box4, $box1Up, $box1Left, $box2Up, $box3Left);
+
+ for (my $i = 0; $i <= 1; $i++) {
+ $picture[$i] = init_graph($xmin, $ymin, $xmax, $ymax, pixels => [ 400, 400 ]);
+
+ $picture[$i]->moveTo($x[0], $y[0]);
+ $picture[$i]->lineTo($x[1], $y[0], 'blue', 3);
+ $picture[$i]->lineTo($x[1], $y[1], 'blue', 3);
+ $picture[$i]->lineTo($x[0], $y[1], 'blue', 3);
+ $picture[$i]->lineTo($x[0], $y[0], 'blue', 3);
+
+ $picture[$i]->moveTo($x[0], $ymax / 2);
+ $picture[$i]->lineTo($x[1], $ymax / 2, 'blue', 2);
+
+ $picture[$i]->moveTo($xmax / 2, $y[0]);
+ $picture[$i]->lineTo($xmax / 2, $y[1], 'blue', 2);
+
+ $squareLength = $x[1] - $x[0];
+ if ($var[0] ne "1") {
+ $box1 = ($co[0] == 1) ? "$var[0]^2" : "$co[0]$var[0]^2";
+ } else {
+ $box1 = ($co[0] == 1) ? "$var[2]^2$var[3]^2" : "$co[0]$var[2]^2$var[3]^2";
+ }
+ $picture[$i]
+ ->lb(new Label($x[0] + $squareLength / 4, $y[1] - $squareLength / 4, "$box1", 'black', 'center', 'middle'));
+
+ my $box4Num = "";
+ my $box4Var = "";
+ if ($var[1] ne "1") {
+ if ($co[2] == 1) { $box4Num = ""; }
+ elsif ($co[2] == -1) { $box4Num = "-"; }
+ else { $box4Num = "$co[2]"; }
+ $box4Var = "$var[1]^2";
+ } else {
+ if ($co[2] == 1) { $box4Num = "1"; }
+ elsif ($co[2] == -1) { $box4Num = "-1"; }
+ else { $box4Num = "$co[2]"; }
+ }
+ $box4 = $box4Num . $box4Var;
+ $picture[$i]
+ ->lb(new Label($x[1] - $squareLength / 4, $y[0] + $squareLength / 4, "$box4", 'black', 'center', 'middle'));
+ }
+
+ if ($acbreakdown[1] == 1) {
+ if ($var[0] ne "1") {
+ $box2 = ($var[1] eq "1") ? "$var[0]" : "$var[0]$var[1]";
+ } else {
+ $box2 = "$var[2]$var[3]";
+ }
+ } elsif ($acbreakdown[1] == -1) {
+ if ($var[0] ne "1") {
+ $box2 = ($var[1] eq "1") ? "-$var[0]" : "-$var[0]$var[1]";
+ } else {
+ $box2 = "-$var[2]$var[3]";
+ }
+ } else {
+ if ($var[0] ne "1") {
+ $box2 = ($var[1] eq "1") ? "$acbreakdown[1]$var[0]" : "$acbreakdown[1]$var[0]$var[1]";
+ } else {
+ $box2 = "$acbreakdown[1]$var[2]$var[3]";
+ }
+ }
+ $picture[1]
+ ->lb(new Label($x[1] - $squareLength / 4, $y[1] - $squareLength / 4, "$box2", 'black', 'center', 'middle'));
+
+ if ($acbreakdown[0] == 1) {
+ if ($var[0] ne "1") {
+ $box3 = ($var[1] eq "1") ? "$var[0]" : "$var[0]$var[1]";
+ } else {
+ $box3 = "$var[2]$var[3]";
+ }
+ } elsif ($acbreakdown[0] == -1) {
+ if ($var[0] ne "1") {
+ $box3 = ($var[1] eq "1") ? "-$var[0]" : "-$var[0]$var[1]";
+ } else {
+ $box3 = "-$var[2]$var[3]";
+ }
+ } else {
+ if ($var[0] ne "1") {
+ $box3 = ($var[1] eq "1") ? "$acbreakdown[0]$var[0]" : "$acbreakdown[0]$var[0]$var[1]";
+ } else {
+ $box3 = "$acbreakdown[0]$var[2]$var[3]";
+ }
+ }
+ $picture[1]
+ ->lb(new Label($x[0] + $squareLength / 4, $y[0] + $squareLength / 4, "$box3", 'black', 'center', 'middle'));
+
+ if ($b[0] == 1) { $box2Up = ($var[1] eq "1") ? "$b[0]" : "$var[1]"; }
+ elsif ($b[0] == -1) { $box2Up = ($var[1] eq "1") ? "$b[0]" : "-$var[1]"; }
+ else { $box2Up = ($var[1] eq "1") ? "$b[0]" : " $b[0]$var[1]"; }
+ $picture[1]->lb(new Label($x[1] - $squareLength / 4, $y[1] + $gap, "$box2Up", 'black', 'center', 'bottom'));
+
+ if ($b[1] == 1) { $box3Left = ($var[1] eq "1") ? "$b[1]" : "$var[1]"; }
+ elsif ($b[1] == -1) { $box3Left = ($var[1] eq "1") ? "$b[1]" : "-$var[1]"; }
+ else { $box3Left = ($var[1] eq "1") ? "$b[1]" : " $b[1]$var[1]"; }
+ $picture[1]->lb(new Label($x[0] - $gap, $y[0] + $squareLength / 4, "$box3Left", 'black', 'right', 'middle'));
+
+ if ($var[0] ne "1") {
+ $box1Up = ($a[0] == 1) ? "$var[0]" : "$a[0]$var[0]";
+ } else {
+ $box1Up = ($a[0] == 1) ? "$var[2]$var[3]" : "$a[0]$var[2]$var[3]";
+ }
+ $picture[1]->lb(new Label($x[0] + $squareLength / 4, $y[1] + $gap, "$box1Up", 'black', 'center', 'bottom'));
+
+ if ($var[0] ne "1") {
+ $box1Left = ($a[1] == 1) ? "$var[0]" : "$a[1]$var[0]";
+ } else {
+ $box1Left = ($a[1] == 1) ? "$var[2]$var[3]" : "$a[1]$var[2]$var[3]";
+ }
+ $picture[1]->lb(new Label($x[0] - $gap, $y[1] - $squareLength / 4, "$box1Left", 'black', 'right', 'middle'));
+
+ $alt1 =
+ "The graph has four generic rectangles. The top left rectangle has $box1 in it, and the bottom right rectangle has $box4 in it.";
+
+ $alt2 =
+ "The graph has four generic rectangles. The top left rectangle has $box1 in it; the top right rectangle has $box2 in it; the bottom left rectangle has $box3 in it; and the bottom right rectangle has $box4 in it. $box1Up is marked above the top left rectangle; $box1Left is marked to the left of the top left rectangle; $box2Up is marked above the top right rectangle; $box3Left is marked to the left of the bottom left rectangle.";
+
+ #### End two pictures ###
+
+ my $factorPairsAOutput = "";
+ my @smallfactorA;
+ my @largefactorA;
+ for my $i (1 .. $sq[0]) {
+ my $j = $co[0] / $i;
+ if ($j == int($j)) { push(@smallfactorA, $i); push(@largefactorA, $j); }
+ }
+
+ for my $i (0 .. $#smallfactorA) {
+ $factorPairsAOutput .= $smallfactorA[$i] . '(' . $largefactorA[$i] . ')';
+ if ($i % 4 == 3) { $factorPairsAOutput .= '\newline'; }
+ else { $factorPairsAOutput .= '&'; }
+ }
+ my $factorPairsCOutput = "";
+ my @smallfactorC;
+ my @largefactorC;
+ for my $i (1 .. $sq[2]) {
+ my $j = $co[2] / $i;
+ if ($j == int($j)) { push(@smallfactorC, $i, -$i); push(@largefactorC, $j, -$j); }
+ }
+
+ for my $i (0 .. $#smallfactorC) {
+ $factorPairsCOutput .= $smallfactorC[$i] . '(' . $largefactorC[$i] . ')';
+ if ($i % 4 == 3) { $factorPairsCOutput .= '\newline'; }
+ else { $factorPairsCOutput .= '&'; }
+ }
+
+ $coOrNot = ($var[1] ne "1") ? "\'s coefficient" : "";
+
+ $outputString .= $BCENTER
+ . image(
+ insertGraph($picture[0]),
+ tex_size => 400,
+ height => 400,
+ width => 400,
+ extra_html_tags => "alt= '$alt1' title= '$alt1'"
+ )
+ . $ECENTER
+ . $PAR
+ . "The first term\'s coefficient, \\("
+ . $co[0]
+ . '\), can be factored into the product of two numbers (where the first factor is positive) in the following ways: \[\begin{array}{llll}'
+ . $factorPairsAOutput
+ . '\end{array}\]'
+ . $PAR
+ . "The third term$coOrNot, \\("
+ . $co[2]
+ . '\), can be factored into the product of two numbers in the following ways: \[\begin{array}{llll}'
+ . $factorPairsCOutput
+ . '\end{array}\]'
+ . $PAR
+ . 'We put each pair into the corresponding places next to those generic rectangles, and try to match the area of those rectangles with \('
+ . $expanded->TeX
+ . '\) by guess-and-check. The following generic rectangles show the solution:'
+ . $PAR
+ . $BCENTER
+ . image(
+ insertGraph($picture[1]),
+ tex_size => 400,
+ height => 400,
+ width => 400,
+ extra_html_tags => "alt= '$alt2' title= '$alt2'"
+ )
+ . $ECENTER
+ . $PAR
+ . 'Dimensions of those generic rectangles in the diagram give us the solution: \[\begin{aligned}[t]'
+ . $expanded->TeX . '&='
+ . $factored->TeX
+ . $factoredSquareOutput
+ . '\end{aligned}\]';
+
+ return $outputString;
+
+}
+
+####
+# End sub factoringMethods
+####
+
+1;
diff --git a/macros/PGcomplexmacros.pl b/macros/math/PGcomplexmacros.pl
similarity index 100%
rename from macros/PGcomplexmacros.pl
rename to macros/math/PGcomplexmacros.pl
diff --git a/macros/PGcomplexmacros2.pl b/macros/math/PGcomplexmacros2.pl
similarity index 100%
rename from macros/PGcomplexmacros2.pl
rename to macros/math/PGcomplexmacros2.pl
diff --git a/macros/PGdiffeqmacros.pl b/macros/math/PGdiffeqmacros.pl
similarity index 100%
rename from macros/PGdiffeqmacros.pl
rename to macros/math/PGdiffeqmacros.pl
diff --git a/macros/PGmatrixmacros.pl b/macros/math/PGmatrixmacros.pl
similarity index 100%
rename from macros/PGmatrixmacros.pl
rename to macros/math/PGmatrixmacros.pl
diff --git a/macros/PGmorematrixmacros.pl b/macros/math/PGmorematrixmacros.pl
similarity index 100%
rename from macros/PGmorematrixmacros.pl
rename to macros/math/PGmorematrixmacros.pl
diff --git a/macros/math/PGnauBinpacking.pl b/macros/math/PGnauBinpacking.pl
new file mode 100644
index 0000000000..a9103ba2b4
--- /dev/null
+++ b/macros/math/PGnauBinpacking.pl
@@ -0,0 +1,188 @@
+sub PGnextfit {
+ my ($binsize, @input) = @_;
+ my (@list, $val);
+
+ $sum = 0;
+ $bin = 1;
+ foreach $val (@input) {
+ $sum += $val;
+ if ($sum > $binsize) {
+ $bin++;
+ $sum = $val;
+ }
+ push @list, $bin;
+ }
+ @list;
+}
+
+sub PGdec_sort {
+ my (@input) = @_;
+ my (@list, @newlist, $val, $newval);
+
+ foreach $val (@input) {
+ $newval = -$val;
+ push @newlist, $newval;
+ }
+ @list1 = num_sort @newlist;
+ foreach $val (@list1) {
+ $newval = -$val;
+ push @list, $newval;
+ }
+ @list;
+}
+
+sub PGdnextfit {
+ my ($binsize, @input) = @_;
+ my (@list, @newlist, $val);
+
+ @newlist = PGdec_sort(@input);
+
+ $sum = 0;
+ $bin = 1;
+ foreach $val (@newlist) {
+ $sum += $val;
+ if ($sum > $binsize) {
+ $bin++;
+ $sum = $val;
+ }
+ push @list, $bin;
+ }
+ @list;
+}
+
+sub PGfirstfit {
+ my ($binsize, @input) = @_;
+ my (@list, @total, $val, $bin, $sum, $size);
+
+ @total = ();
+ @list = ();
+
+ foreach $val (@input) {
+ $bin = -1;
+ $size = scalar @total;
+
+ do {
+ $bin++;
+ if ($bin > $size - 1) { push @total, 0; }
+ $sum = $total[$bin] + $val;
+ } while ($sum > $binsize);
+
+ $total[$bin] += $val;
+ push @list, $bin + 1;
+ }
+
+ @list;
+}
+
+sub PGdfirstfit {
+ my ($binsize, @input) = @_;
+ my (@list, @newlist, @total, $bin, $val, $sum, $size);
+
+ @total = ();
+
+ @newlist = PGdec_sort(@input);
+ foreach $val (@newlist) {
+ $bin = -1;
+ $size = scalar @total;
+ do {
+ $bin++;
+ if ($bin > $size - 1) { push @total, 0; }
+ $sum = $total[$bin] + $val;
+
+ } while ($sum > $binsize);
+ $total[$bin] += $val;
+ push @list, $bin + 1;
+ }
+ @list;
+}
+
+sub PGworstfit {
+ my ($binsize, @input) = @_;
+ my ($minbin, $bin, $sum, $minloc, $val, $minval, $a, @total, @bins);
+
+ @total = (0);
+ $minval = 0;
+ $minbin = 0;
+ $bin = 0;
+
+ foreach $val (@input) {
+
+ $sum = $minval + $val;
+
+ if ($sum > $binsize) {
+ push @total, $val;
+ $bin = scalar @total - 1;
+
+ if ($val < $minval) {
+ $minbin = scalar @total - 1;
+ $minval = $val;
+ } else {
+ $minval = $total[$minbin];
+ }
+ } else {
+ $total[$minbin] += $val;
+ $bin = $minbin;
+ if ($total[$minbin] < min(@total)) {
+ $minval = $total[$minbin];
+ } else {
+ $minloc = -1;
+ $a = min(@total);
+ do {
+ $minloc++;
+ } until ($total[$minloc] == $a);
+ $minbin = $minloc;
+ $minval = $total[$minbin];
+ }
+ }
+ push @bins, $bin + 1;
+ }
+ @bins;
+}
+
+sub PGdworstfit {
+ my ($binsize, @input) = @_;
+ my ($minbin, $bin, $sum, $minloc, $val, $minval, $a, @total, @bins, @newlist);
+
+ @newlist = PGdec_sort(@input);
+
+ @total = (0);
+ $minval = 0;
+ $minbin = 0;
+ $bin = 0;
+
+ foreach $val (@newlist) {
+
+ $sum = $minval + $val;
+
+ if ($sum > $binsize) {
+ push @total, $val;
+ $bin = scalar @total - 1;
+
+ if ($val < $minval) {
+ $minbin = scalar @total - 1;
+ $minval = $val;
+ } else {
+ $minval = $total[$minbin];
+ }
+ } else {
+ $total[$minbin] += $val;
+ $bin = $minbin;
+ if ($total[$minbin] < min(@total)) {
+ $minval = $total[$minbin];
+ } else {
+ $minloc = -1;
+ $a = min(@total);
+ do {
+ $minloc++;
+ } until ($total[$minloc] == $a);
+ $minbin = $minloc;
+ $minval = $total[$minbin];
+ }
+ }
+ push @bins, $bin + 1;
+
+ }
+ @bins;
+}
+
+1;
diff --git a/macros/math/PGnauGraphCatalog.pl b/macros/math/PGnauGraphCatalog.pl
new file mode 100644
index 0000000000..c8f705fd11
--- /dev/null
+++ b/macros/math/PGnauGraphCatalog.pl
@@ -0,0 +1,1256 @@
+# All simple graphs with fewer than 8 vertices
+
+push @graph1, "0;";
+
+push @graph2, "0 0;0 0;", "0 1;1 0;";
+
+push @graph3, "0 0 0;0 0 0;0 0 0;", "0 0 1;0 0 0;1 0 0;", "0 0 1;0 0 1;1 1 0;", "0 1 1;1 0 1;1 1 0;";
+
+push @graph4, "0 0 0 0;0 0 0 0;0 0 0 0;0 0 0 0;",
+ "0 0 0 1;0 0 0 0;0 0 0 0;1 0 0 0;",
+ "0 0 0 1;0 0 0 1;0 0 0 0;1 1 0 0;",
+ "0 0 0 1;0 0 0 1;0 0 0 1;1 1 1 0;",
+ "0 0 1 0;0 0 0 1;1 0 0 0;0 1 0 0;",
+ "0 0 1 1;0 0 0 1;1 0 0 0;1 1 0 0;",
+ "0 0 1 1;0 0 0 0;1 0 0 1;1 0 1 0;",
+ "0 0 1 1;0 0 0 1;1 0 0 1;1 1 1 0;",
+ "0 0 1 1;0 0 1 1;1 1 0 0;1 1 0 0;",
+ "0 0 1 1;0 0 1 1;1 1 0 1;1 1 1 0;",
+ "0 1 1 1;1 0 1 1;1 1 0 1;1 1 1 0;";
+
+push @graph5, "0 0 0 0 0;0 0 0 0 0;0 0 0 0 0;0 0 0 0 0;0 0 0 0 0;",
+ "0 0 0 0 1;0 0 0 0 0;0 0 0 0 0;0 0 0 0 0;1 0 0 0 0;",
+ "0 0 0 0 1;0 0 0 0 1;0 0 0 0 0;0 0 0 0 0;1 1 0 0 0;",
+ "0 0 0 0 1;0 0 0 0 1;0 0 0 0 1;0 0 0 0 0;1 1 1 0 0;",
+ "0 0 0 0 1;0 0 0 0 1;0 0 0 0 1;0 0 0 0 1;1 1 1 1 0;",
+ "0 0 0 1 0;0 0 0 0 1;0 0 0 0 0;1 0 0 0 0;0 1 0 0 0;",
+ "0 0 0 1 1;0 0 0 0 1;0 0 0 0 0;1 0 0 0 0;1 1 0 0 0;",
+ "0 0 0 1 0;0 0 0 0 1;0 0 0 0 1;1 0 0 0 0;0 1 1 0 0;",
+ "0 0 0 1 1;0 0 0 0 0;0 0 0 0 0;1 0 0 0 1;1 0 0 1 0;",
+ "0 0 0 1 1;0 0 0 0 1;0 0 0 0 1;1 0 0 0 0;1 1 1 0 0;",
+ "0 0 0 1 1;0 0 0 0 1;0 0 0 0 0;1 0 0 0 1;1 1 0 1 0;",
+ "0 0 0 1 1;0 0 0 0 1;0 0 0 0 1;1 0 0 0 1;1 1 1 1 0;",
+ "0 0 0 1 1;0 0 0 1 1;0 0 0 0 0;1 1 0 0 0;1 1 0 0 0;",
+ "0 0 0 1 1;0 0 0 1 1;0 0 0 0 1;1 1 0 0 0;1 1 1 0 0;",
+ "0 0 0 1 1;0 0 0 1 1;0 0 0 0 0;1 1 0 0 1;1 1 0 1 0;",
+ "0 0 0 1 1;0 0 0 1 0;0 0 0 0 1;1 1 0 0 1;1 0 1 1 0;",
+ "0 0 0 1 1;0 0 0 1 1;0 0 0 0 1;1 1 0 0 1;1 1 1 1 0;",
+ "0 0 0 1 1;0 0 0 1 1;0 0 0 1 1;1 1 1 0 0;1 1 1 0 0;",
+ "0 0 0 1 1;0 0 0 1 1;0 0 0 1 1;1 1 1 0 1;1 1 1 1 0;",
+ "0 0 1 0 1;0 0 0 1 1;1 0 0 0 0;0 1 0 0 0;1 1 0 0 0;",
+ "0 0 1 0 1;0 0 0 1 0;1 0 0 0 1;0 1 0 0 0;1 0 1 0 0;",
+ "0 0 1 0 1;0 0 0 1 1;1 0 0 0 1;0 1 0 0 0;1 1 1 0 0;",
+ "0 0 1 0 1;0 0 0 1 1;1 0 0 0 1;0 1 0 0 1;1 1 1 1 0;",
+ "0 0 1 1 0;0 0 0 1 1;1 0 0 0 1;1 1 0 0 0;0 1 1 0 0;",
+ "0 0 1 1 1;0 0 0 1 1;1 0 0 0 1;1 1 0 0 0;1 1 1 0 0;",
+ "0 0 1 1 1;0 0 0 1 1;1 0 0 0 1;1 1 0 0 1;1 1 1 1 0;",
+ "0 0 1 1 1;0 0 0 0 1;1 0 0 1 1;1 0 1 0 0;1 1 1 0 0;",
+ "0 0 1 1 1;0 0 0 0 0;1 0 0 1 1;1 0 1 0 1;1 0 1 1 0;",
+ "0 0 1 1 1;0 0 0 0 1;1 0 0 1 1;1 0 1 0 1;1 1 1 1 0;",
+ "0 0 1 1 1;0 0 0 1 1;1 0 0 1 1;1 1 1 0 1;1 1 1 1 0;",
+ "0 0 1 1 1;0 0 1 1 1;1 1 0 0 1;1 1 0 0 0;1 1 1 0 0;",
+ "0 0 1 1 1;0 0 1 1 1;1 1 0 0 1;1 1 0 0 1;1 1 1 1 0;",
+ "0 0 1 1 1;0 0 1 1 1;1 1 0 1 1;1 1 1 0 1;1 1 1 1 0;",
+ "0 1 1 1 1;1 0 1 1 1;1 1 0 1 1;1 1 1 0 1;1 1 1 1 0;";
+
+push @graph6, "0 0 0 0 0 0;0 0 0 0 0 0;0 0 0 0 0 0;0 0 0 0 0 0;0 0 0 0 0 0;0 0 0 0 0 0;",
+ "0 0 0 0 0 1;0 0 0 0 0 0;0 0 0 0 0 0;0 0 0 0 0 0;0 0 0 0 0 0;1 0 0 0 0 0;",
+ "0 0 0 0 0 1;0 0 0 0 0 1;0 0 0 0 0 0;0 0 0 0 0 0;0 0 0 0 0 0;1 1 0 0 0 0;",
+ "0 0 0 0 0 1;0 0 0 0 0 1;0 0 0 0 0 1;0 0 0 0 0 0;0 0 0 0 0 0;1 1 1 0 0 0;",
+ "0 0 0 0 0 1;0 0 0 0 0 1;0 0 0 0 0 1;0 0 0 0 0 1;0 0 0 0 0 0;1 1 1 1 0 0;",
+ "0 0 0 0 0 1;0 0 0 0 0 1;0 0 0 0 0 1;0 0 0 0 0 1;0 0 0 0 0 1;1 1 1 1 1 0;",
+ "0 0 0 0 1 0;0 0 0 0 0 1;0 0 0 0 0 0;0 0 0 0 0 0;1 0 0 0 0 0;0 1 0 0 0 0;",
+ "0 0 0 0 1 1;0 0 0 0 0 1;0 0 0 0 0 0;0 0 0 0 0 0;1 0 0 0 0 0;1 1 0 0 0 0;",
+ "0 0 0 0 1 0;0 0 0 0 0 1;0 0 0 0 0 1;0 0 0 0 0 0;1 0 0 0 0 0;0 1 1 0 0 0;",
+ "0 0 0 0 1 1;0 0 0 0 0 0;0 0 0 0 0 0;0 0 0 0 0 0;1 0 0 0 0 1;1 0 0 0 1 0;",
+ "0 0 0 0 1 1;0 0 0 0 0 1;0 0 0 0 0 1;0 0 0 0 0 0;1 0 0 0 0 0;1 1 1 0 0 0;",
+ "0 0 0 0 1 0;0 0 0 0 0 1;0 0 0 0 0 1;0 0 0 0 0 1;1 0 0 0 0 0;0 1 1 1 0 0;",
+ "0 0 0 0 1 1;0 0 0 0 0 1;0 0 0 0 0 0;0 0 0 0 0 0;1 0 0 0 0 1;1 1 0 0 1 0;",
+ "0 0 0 0 1 1;0 0 0 0 0 1;0 0 0 0 0 1;0 0 0 0 0 1;1 0 0 0 0 0;1 1 1 1 0 0;",
+ "0 0 0 0 1 1;0 0 0 0 0 1;0 0 0 0 0 1;0 0 0 0 0 0;1 0 0 0 0 1;1 1 1 0 1 0;",
+ "0 0 0 0 1 1;0 0 0 0 0 1;0 0 0 0 0 1;0 0 0 0 0 1;1 0 0 0 0 1;1 1 1 1 1 0;",
+ "0 0 0 0 1 1;0 0 0 0 1 1;0 0 0 0 0 0;0 0 0 0 0 0;1 1 0 0 0 0;1 1 0 0 0 0;",
+ "0 0 0 0 1 0;0 0 0 0 1 0;0 0 0 0 0 1;0 0 0 0 0 1;1 1 0 0 0 0;0 0 1 1 0 0;",
+ "0 0 0 0 1 1;0 0 0 0 1 1;0 0 0 0 0 1;0 0 0 0 0 0;1 1 0 0 0 0;1 1 1 0 0 0;",
+ "0 0 0 0 1 1;0 0 0 0 1 0;0 0 0 0 0 1;0 0 0 0 0 1;1 1 0 0 0 0;1 0 1 1 0 0;",
+ "0 0 0 0 1 1;0 0 0 0 1 1;0 0 0 0 0 0;0 0 0 0 0 0;1 1 0 0 0 1;1 1 0 0 1 0;",
+ "0 0 0 0 1 1;0 0 0 0 1 0;0 0 0 0 0 1;0 0 0 0 0 0;1 1 0 0 0 1;1 0 1 0 1 0;",
+ "0 0 0 0 1 0;0 0 0 0 1 0;0 0 0 0 0 1;0 0 0 0 0 1;1 1 0 0 0 1;0 0 1 1 1 0;",
+ "0 0 0 0 1 1;0 0 0 0 1 1;0 0 0 0 0 1;0 0 0 0 0 1;1 1 0 0 0 0;1 1 1 1 0 0;",
+ "0 0 0 0 1 1;0 0 0 0 1 1;0 0 0 0 0 1;0 0 0 0 0 0;1 1 0 0 0 1;1 1 1 0 1 0;",
+ "0 0 0 0 1 1;0 0 0 0 1 0;0 0 0 0 0 1;0 0 0 0 0 1;1 1 0 0 0 1;1 0 1 1 1 0;",
+ "0 0 0 0 1 1;0 0 0 0 1 1;0 0 0 0 0 1;0 0 0 0 0 1;1 1 0 0 0 1;1 1 1 1 1 0;",
+ "0 0 0 0 1 1;0 0 0 0 1 1;0 0 0 0 1 1;0 0 0 0 0 0;1 1 1 0 0 0;1 1 1 0 0 0;",
+ "0 0 0 0 1 1;0 0 0 0 1 1;0 0 0 0 1 0;0 0 0 0 0 1;1 1 1 0 0 0;1 1 0 1 0 0;",
+ "0 0 0 0 1 1;0 0 0 0 1 1;0 0 0 0 1 1;0 0 0 0 0 1;1 1 1 0 0 0;1 1 1 1 0 0;",
+ "0 0 0 0 1 1;0 0 0 0 1 1;0 0 0 0 1 1;0 0 0 0 0 0;1 1 1 0 0 1;1 1 1 0 1 0;",
+ "0 0 0 0 1 1;0 0 0 0 1 1;0 0 0 0 1 0;0 0 0 0 0 1;1 1 1 0 0 1;1 1 0 1 1 0;",
+ "0 0 0 0 1 1;0 0 0 0 1 1;0 0 0 0 1 1;0 0 0 0 0 1;1 1 1 0 0 1;1 1 1 1 1 0;",
+ "0 0 0 0 1 1;0 0 0 0 1 1;0 0 0 0 1 1;0 0 0 0 1 1;1 1 1 1 0 0;1 1 1 1 0 0;",
+ "0 0 0 0 1 1;0 0 0 0 1 1;0 0 0 0 1 1;0 0 0 0 1 1;1 1 1 1 0 1;1 1 1 1 1 0;",
+ "0 0 0 1 0 0;0 0 0 0 1 0;0 0 0 0 0 1;1 0 0 0 0 0;0 1 0 0 0 0;0 0 1 0 0 0;",
+ "0 0 0 1 0 1;0 0 0 0 1 1;0 0 0 0 0 0;1 0 0 0 0 0;0 1 0 0 0 0;1 1 0 0 0 0;",
+ "0 0 0 1 0 1;0 0 0 0 1 0;0 0 0 0 0 1;1 0 0 0 0 0;0 1 0 0 0 0;1 0 1 0 0 0;",
+ "0 0 0 1 0 1;0 0 0 0 1 0;0 0 0 0 0 0;1 0 0 0 0 1;0 1 0 0 0 0;1 0 0 1 0 0;",
+ "0 0 0 1 0 1;0 0 0 0 1 1;0 0 0 0 0 1;1 0 0 0 0 0;0 1 0 0 0 0;1 1 1 0 0 0;",
+ "0 0 0 1 0 1;0 0 0 0 1 1;0 0 0 0 0 0;1 0 0 0 0 1;0 1 0 0 0 0;1 1 0 1 0 0;",
+ "0 0 0 1 0 1;0 0 0 0 1 0;0 0 0 0 0 1;1 0 0 0 0 1;0 1 0 0 0 0;1 0 1 1 0 0;",
+ "0 0 0 1 0 1;0 0 0 0 1 1;0 0 0 0 0 1;1 0 0 0 0 1;0 1 0 0 0 0;1 1 1 1 0 0;",
+ "0 0 0 1 0 1;0 0 0 0 1 1;0 0 0 0 0 0;1 0 0 0 0 1;0 1 0 0 0 1;1 1 0 1 1 0;",
+ "0 0 0 1 0 1;0 0 0 0 1 1;0 0 0 0 0 1;1 0 0 0 0 1;0 1 0 0 0 1;1 1 1 1 1 0;",
+ "0 0 0 1 1 0;0 0 0 0 1 1;0 0 0 0 0 0;1 0 0 0 0 1;1 1 0 0 0 0;0 1 0 1 0 0;",
+ "0 0 0 1 1 1;0 0 0 0 1 1;0 0 0 0 0 1;1 0 0 0 0 0;1 1 0 0 0 0;1 1 1 0 0 0;",
+ "0 0 0 1 1 1;0 0 0 0 1 1;0 0 0 0 0 0;1 0 0 0 0 1;1 1 0 0 0 0;1 1 0 1 0 0;",
+ "0 0 0 1 1 0;0 0 0 0 1 1;0 0 0 0 0 1;1 0 0 0 0 1;1 1 0 0 0 0;0 1 1 1 0 0;",
+ "0 0 0 1 1 1;0 0 0 0 1 1;0 0 0 0 0 0;1 0 0 0 0 0;1 1 0 0 0 1;1 1 0 0 1 0;",
+ "0 0 0 1 1 1;0 0 0 0 1 0;0 0 0 0 0 1;1 0 0 0 0 0;1 1 0 0 0 1;1 0 1 0 1 0;",
+ "0 0 0 1 1 1;0 0 0 0 1 1;0 0 0 0 0 1;1 0 0 0 0 1;1 1 0 0 0 0;1 1 1 1 0 0;",
+ "0 0 0 1 1 1;0 0 0 0 1 1;0 0 0 0 0 1;1 0 0 0 0 0;1 1 0 0 0 1;1 1 1 0 1 0;",
+ "0 0 0 1 1 1;0 0 0 0 1 1;0 0 0 0 0 0;1 0 0 0 0 1;1 1 0 0 0 1;1 1 0 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 0 1 1;0 0 0 0 0 1;1 0 0 0 0 1;1 1 0 0 0 1;1 1 1 1 1 0;",
+ "0 0 0 1 0 1;0 0 0 0 1 1;0 0 0 0 1 0;1 0 0 0 0 0;0 1 1 0 0 0;1 1 0 0 0 0;",
+ "0 0 0 1 0 0;0 0 0 0 1 1;0 0 0 0 1 1;1 0 0 0 0 0;0 1 1 0 0 0;0 1 1 0 0 0;",
+ "0 0 0 1 0 1;0 0 0 0 1 0;0 0 0 0 1 0;1 0 0 0 0 1;0 1 1 0 0 0;1 0 0 1 0 0;",
+ "0 0 0 1 0 1;0 0 0 0 1 1;0 0 0 0 1 1;1 0 0 0 0 0;0 1 1 0 0 0;1 1 1 0 0 0;",
+ "0 0 0 1 0 1;0 0 0 0 1 1;0 0 0 0 1 0;1 0 0 0 0 1;0 1 1 0 0 0;1 1 0 1 0 0;",
+ "0 0 0 1 0 1;0 0 0 0 1 1;0 0 0 0 1 0;1 0 0 0 0 0;0 1 1 0 0 1;1 1 0 0 1 0;",
+ "0 0 0 1 0 0;0 0 0 0 1 1;0 0 0 0 1 1;1 0 0 0 0 0;0 1 1 0 0 1;0 1 1 0 1 0;",
+ "0 0 0 1 0 1;0 0 0 0 1 0;0 0 0 0 1 0;1 0 0 0 0 1;0 1 1 0 0 1;1 0 0 1 1 0;",
+ "0 0 0 1 0 1;0 0 0 0 1 1;0 0 0 0 1 1;1 0 0 0 0 1;0 1 1 0 0 0;1 1 1 1 0 0;",
+ "0 0 0 1 0 1;0 0 0 0 1 1;0 0 0 0 1 1;1 0 0 0 0 0;0 1 1 0 0 1;1 1 1 0 1 0;",
+ "0 0 0 1 0 1;0 0 0 0 1 1;0 0 0 0 1 0;1 0 0 0 0 1;0 1 1 0 0 1;1 1 0 1 1 0;",
+ "0 0 0 1 0 1;0 0 0 0 1 1;0 0 0 0 1 1;1 0 0 0 0 1;0 1 1 0 0 1;1 1 1 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 0 0 0;0 0 0 0 0 0;1 0 0 0 1 1;1 0 0 1 0 1;1 0 0 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 0 0 1;0 0 0 0 0 1;1 0 0 0 1 1;1 0 0 1 0 0;1 1 1 1 0 0;",
+ "0 0 0 1 1 1;0 0 0 0 0 1;0 0 0 0 0 0;1 0 0 0 1 1;1 0 0 1 0 1;1 1 0 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 0 0 1;0 0 0 0 0 1;1 0 0 0 1 1;1 0 0 1 0 1;1 1 1 1 1 0;",
+ "0 0 0 1 1 0;0 0 0 0 1 1;0 0 0 0 1 1;1 0 0 0 0 1;1 1 1 0 0 0;0 1 1 1 0 0;",
+ "0 0 0 1 1 1;0 0 0 0 1 1;0 0 0 0 1 1;1 0 0 0 0 1;1 1 1 0 0 0;1 1 1 1 0 0;",
+ "0 0 0 1 1 1;0 0 0 0 1 1;0 0 0 0 1 1;1 0 0 0 0 0;1 1 1 0 0 1;1 1 1 0 1 0;",
+ "0 0 0 1 1 1;0 0 0 0 1 1;0 0 0 0 1 0;1 0 0 0 0 1;1 1 1 0 0 1;1 1 0 1 1 0;",
+ "0 0 0 1 1 0;0 0 0 0 1 1;0 0 0 0 1 1;1 0 0 0 0 1;1 1 1 0 0 1;0 1 1 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 0 1 1;0 0 0 0 1 1;1 0 0 0 0 1;1 1 1 0 0 1;1 1 1 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 0 1 1;0 0 0 0 0 1;1 0 0 0 1 1;1 1 0 1 0 0;1 1 1 1 0 0;",
+ "0 0 0 1 1 1;0 0 0 0 1 1;0 0 0 0 0 0;1 0 0 0 1 1;1 1 0 1 0 1;1 1 0 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 0 1 0;0 0 0 0 0 1;1 0 0 0 1 1;1 1 0 1 0 1;1 0 1 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 0 1 1;0 0 0 0 0 1;1 0 0 0 1 1;1 1 0 1 0 1;1 1 1 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 0 1 1;0 0 0 0 1 1;1 0 0 0 1 1;1 1 1 1 0 0;1 1 1 1 0 0;",
+ "0 0 0 1 1 1;0 0 0 0 1 1;0 0 0 0 1 1;1 0 0 0 1 1;1 1 1 1 0 1;1 1 1 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 1 1 1;0 0 0 0 0 1;1 1 0 0 0 0;1 1 0 0 0 0;1 1 1 0 0 0;",
+ "0 0 0 1 1 1;0 0 0 1 1 1;0 0 0 0 0 0;1 1 0 0 0 1;1 1 0 0 0 0;1 1 0 1 0 0;",
+ "0 0 0 1 1 1;0 0 0 1 1 1;0 0 0 0 0 1;1 1 0 0 0 1;1 1 0 0 0 0;1 1 1 1 0 0;",
+ "0 0 0 1 1 1;0 0 0 1 1 1;0 0 0 0 0 0;1 1 0 0 0 1;1 1 0 0 0 1;1 1 0 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 1 1 1;0 0 0 0 0 1;1 1 0 0 0 1;1 1 0 0 0 1;1 1 1 1 1 0;",
+ "0 0 0 1 1 0;0 0 0 1 0 1;0 0 0 0 1 1;1 1 0 0 0 0;1 0 1 0 0 0;0 1 1 0 0 0;",
+ "0 0 0 1 1 1;0 0 0 1 0 1;0 0 0 0 1 1;1 1 0 0 0 0;1 0 1 0 0 0;1 1 1 0 0 0;",
+ "0 0 0 1 1 1;0 0 0 1 0 0;0 0 0 0 1 1;1 1 0 0 0 1;1 0 1 0 0 0;1 0 1 1 0 0;",
+ "0 0 0 1 1 0;0 0 0 1 0 1;0 0 0 0 1 1;1 1 0 0 0 1;1 0 1 0 0 0;0 1 1 1 0 0;",
+ "0 0 0 1 1 1;0 0 0 1 0 0;0 0 0 0 1 0;1 1 0 0 0 1;1 0 1 0 0 1;1 0 0 1 1 0;",
+ "0 0 0 1 1 0;0 0 0 1 0 1;0 0 0 0 1 0;1 1 0 0 0 1;1 0 1 0 0 1;0 1 0 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 1 0 1;0 0 0 0 1 1;1 1 0 0 0 1;1 0 1 0 0 0;1 1 1 1 0 0;",
+ "0 0 0 1 1 1;0 0 0 1 0 1;0 0 0 0 1 0;1 1 0 0 0 1;1 0 1 0 0 1;1 1 0 1 1 0;",
+ "0 0 0 1 1 0;0 0 0 1 0 1;0 0 0 0 1 1;1 1 0 0 0 1;1 0 1 0 0 1;0 1 1 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 1 0 1;0 0 0 0 1 1;1 1 0 0 0 1;1 0 1 0 0 1;1 1 1 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 1 1 1;0 0 0 0 1 1;1 1 0 0 0 0;1 1 1 0 0 0;1 1 1 0 0 0;",
+ "0 0 0 1 1 1;0 0 0 1 1 1;0 0 0 0 1 0;1 1 0 0 0 1;1 1 1 0 0 0;1 1 0 1 0 0;",
+ "0 0 0 1 1 1;0 0 0 1 1 1;0 0 0 0 1 1;1 1 0 0 0 1;1 1 1 0 0 0;1 1 1 1 0 0;",
+ "0 0 0 1 1 1;0 0 0 1 1 1;0 0 0 0 1 1;1 1 0 0 0 0;1 1 1 0 0 1;1 1 1 0 1 0;",
+ "0 0 0 1 1 1;0 0 0 1 1 1;0 0 0 0 1 1;1 1 0 0 0 1;1 1 1 0 0 1;1 1 1 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 1 1 1;0 0 0 0 0 1;1 1 0 0 1 1;1 1 0 1 0 0;1 1 1 1 0 0;",
+ "0 0 0 1 1 1;0 0 0 1 1 1;0 0 0 0 0 0;1 1 0 0 1 1;1 1 0 1 0 1;1 1 0 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 1 1 0;0 0 0 0 0 1;1 1 0 0 1 1;1 1 0 1 0 1;1 0 1 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 1 1 1;0 0 0 0 0 1;1 1 0 0 1 1;1 1 0 1 0 1;1 1 1 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 1 0 1;0 0 0 0 1 1;1 1 0 0 1 1;1 0 1 1 0 0;1 1 1 1 0 0;",
+ "0 0 0 1 1 0;0 0 0 1 0 1;0 0 0 0 1 1;1 1 0 0 1 1;1 0 1 1 0 1;0 1 1 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 1 0 1;0 0 0 0 1 1;1 1 0 0 1 1;1 0 1 1 0 1;1 1 1 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 1 1 1;0 0 0 0 1 1;1 1 0 0 1 1;1 1 1 1 0 1;1 1 1 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 1 1 1;0 0 0 1 1 1;1 1 1 0 0 0;1 1 1 0 0 0;1 1 1 0 0 0;",
+ "0 0 0 1 1 1;0 0 0 1 1 1;0 0 0 1 1 1;1 1 1 0 0 1;1 1 1 0 0 0;1 1 1 1 0 0;",
+ "0 0 0 1 1 1;0 0 0 1 1 1;0 0 0 1 1 0;1 1 1 0 0 1;1 1 1 0 0 1;1 1 0 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 1 1 1;0 0 0 1 1 1;1 1 1 0 0 1;1 1 1 0 0 1;1 1 1 1 1 0;",
+ "0 0 0 1 1 1;0 0 0 1 1 1;0 0 0 1 1 1;1 1 1 0 1 1;1 1 1 1 0 1;1 1 1 1 1 0;",
+ "0 0 1 0 1 0;0 0 0 1 0 1;1 0 0 0 1 0;0 1 0 0 0 1;1 0 1 0 0 0;0 1 0 1 0 0;",
+ "0 0 1 0 1 1;0 0 0 1 0 1;1 0 0 0 1 1;0 1 0 0 0 0;1 0 1 0 0 0;1 1 1 0 0 0;",
+ "0 0 1 0 1 1;0 0 0 1 0 1;1 0 0 0 1 0;0 1 0 0 0 1;1 0 1 0 0 0;1 1 0 1 0 0;",
+ "0 0 1 0 1 1;0 0 0 1 0 0;1 0 0 0 1 1;0 1 0 0 0 0;1 0 1 0 0 1;1 0 1 0 1 0;",
+ "0 0 1 0 1 1;0 0 0 1 0 1;1 0 0 0 1 1;0 1 0 0 0 1;1 0 1 0 0 0;1 1 1 1 0 0;",
+ "0 0 1 0 1 1;0 0 0 1 0 1;1 0 0 0 1 1;0 1 0 0 0 0;1 0 1 0 0 1;1 1 1 0 1 0;",
+ "0 0 1 0 1 1;0 0 0 1 0 1;1 0 0 0 1 1;0 1 0 0 0 1;1 0 1 0 0 1;1 1 1 1 1 0;",
+ "0 0 1 0 1 1;0 0 0 1 1 1;1 0 0 0 1 0;0 1 0 0 0 1;1 1 1 0 0 0;1 1 0 1 0 0;",
+ "0 0 1 0 1 1;0 0 0 1 1 1;1 0 0 0 1 1;0 1 0 0 0 1;1 1 1 0 0 0;1 1 1 1 0 0;",
+ "0 0 1 0 1 1;0 0 0 1 1 1;1 0 0 0 1 1;0 1 0 0 0 0;1 1 1 0 0 1;1 1 1 0 1 0;",
+ "0 0 1 0 1 1;0 0 0 1 1 1;1 0 0 0 1 0;0 1 0 0 0 1;1 1 1 0 0 1;1 1 0 1 1 0;",
+ "0 0 1 0 1 1;0 0 0 1 1 0;1 0 0 0 1 1;0 1 0 0 0 1;1 1 1 0 0 1;1 0 1 1 1 0;",
+ "0 0 1 0 1 1;0 0 0 1 1 1;1 0 0 0 1 1;0 1 0 0 0 1;1 1 1 0 0 1;1 1 1 1 1 0;",
+ "0 0 1 0 1 1;0 0 0 1 1 1;1 0 0 0 1 1;0 1 0 0 1 1;1 1 1 1 0 0;1 1 1 1 0 0;",
+ "0 0 1 0 1 1;0 0 0 1 1 1;1 0 0 0 1 1;0 1 0 0 1 1;1 1 1 1 0 1;1 1 1 1 1 0;",
+ "0 0 1 1 0 1;0 0 0 1 1 1;1 0 0 0 1 1;1 1 0 0 0 0;0 1 1 0 0 0;1 1 1 0 0 0;",
+ "0 0 1 1 0 1;0 0 0 1 1 1;1 0 0 0 1 0;1 1 0 0 0 1;0 1 1 0 0 0;1 1 0 1 0 0;",
+ "0 0 1 1 0 1;0 0 0 1 1 1;1 0 0 0 1 1;1 1 0 0 0 1;0 1 1 0 0 0;1 1 1 1 0 0;",
+ "0 0 1 1 0 1;0 0 0 1 1 1;1 0 0 0 1 1;1 1 0 0 0 1;0 1 1 0 0 1;1 1 1 1 1 0;",
+ "0 0 1 1 1 0;0 0 0 1 1 1;1 0 0 0 1 1;1 1 0 0 0 1;1 1 1 0 0 0;0 1 1 1 0 0;",
+ "0 0 1 1 1 1;0 0 0 1 1 1;1 0 0 0 1 1;1 1 0 0 0 1;1 1 1 0 0 0;1 1 1 1 0 0;",
+ "0 0 1 1 1 1;0 0 0 1 1 1;1 0 0 0 1 0;1 1 0 0 0 1;1 1 1 0 0 1;1 1 0 1 1 0;",
+ "0 0 1 1 1 1;0 0 0 1 1 1;1 0 0 0 1 1;1 1 0 0 0 1;1 1 1 0 0 1;1 1 1 1 1 0;",
+ "0 0 1 1 1 1;0 0 0 1 1 1;1 0 0 0 1 1;1 1 0 0 1 1;1 1 1 1 0 1;1 1 1 1 1 0;",
+ "0 0 1 1 1 1;0 0 0 0 1 1;1 0 0 1 1 1;1 0 1 0 0 1;1 1 1 0 0 0;1 1 1 1 0 0;",
+ "0 0 1 1 1 1;0 0 0 0 1 1;1 0 0 1 1 1;1 0 1 0 0 0;1 1 1 0 0 1;1 1 1 0 1 0;",
+ "0 0 1 1 1 1;0 0 0 0 1 1;1 0 0 1 1 1;1 0 1 0 0 1;1 1 1 0 0 1;1 1 1 1 1 0;",
+ "0 0 1 1 1 1;0 0 0 0 0 1;1 0 0 1 1 1;1 0 1 0 1 1;1 0 1 1 0 0;1 1 1 1 0 0;",
+ "0 0 1 1 1 1;0 0 0 0 0 0;1 0 0 1 1 1;1 0 1 0 1 1;1 0 1 1 0 1;1 0 1 1 1 0;",
+ "0 0 1 1 1 1;0 0 0 0 0 1;1 0 0 1 1 1;1 0 1 0 1 1;1 0 1 1 0 1;1 1 1 1 1 0;",
+ "0 0 1 1 1 1;0 0 0 0 1 1;1 0 0 1 1 1;1 0 1 0 1 1;1 1 1 1 0 1;1 1 1 1 1 0;",
+ "0 0 1 1 1 1;0 0 0 1 1 1;1 0 0 1 1 1;1 1 1 0 1 1;1 1 1 1 0 1;1 1 1 1 1 0;",
+ "0 0 1 1 1 1;0 0 1 1 1 1;1 1 0 0 1 1;1 1 0 0 0 1;1 1 1 0 0 0;1 1 1 1 0 0;",
+ "0 0 1 1 1 1;0 0 1 1 1 1;1 1 0 0 1 1;1 1 0 0 0 0;1 1 1 0 0 1;1 1 1 0 1 0;",
+ "0 0 1 1 1 1;0 0 1 1 1 0;1 1 0 0 1 1;1 1 0 0 0 1;1 1 1 0 0 1;1 0 1 1 1 0;",
+ "0 0 1 1 1 1;0 0 1 1 1 1;1 1 0 0 1 1;1 1 0 0 0 1;1 1 1 0 0 1;1 1 1 1 1 0;",
+ "0 0 1 1 1 1;0 0 1 1 1 1;1 1 0 0 1 1;1 1 0 0 1 1;1 1 1 1 0 0;1 1 1 1 0 0;",
+ "0 0 1 1 1 1;0 0 1 1 1 1;1 1 0 0 1 1;1 1 0 0 1 1;1 1 1 1 0 1;1 1 1 1 1 0;",
+ "0 0 1 1 1 1;0 0 1 1 1 1;1 1 0 1 1 1;1 1 1 0 1 1;1 1 1 1 0 1;1 1 1 1 1 0;",
+ "0 1 1 1 1 1;1 0 1 1 1 1;1 1 0 1 1 1;1 1 1 0 1 1;1 1 1 1 0 1;1 1 1 1 1 0;";
+
+push @graph7, "0 0 0 0 0 0 0;0 0 0 0 0 0 0;0 0 0 0 0 0 0;0 0 0 0 0 0 0;0 0 0 0 0 0 0;0 0 0 0 0 0 0;0 0 0 0 0 0 0;",
+ "0 0 0 0 0 0 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;0 0 0 0 0 0 0;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 0 0 0 0 0 0;",
+ "0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 1 0 0 0 0 0;",
+ "0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 0 0 0 0 0 0;0 1 0 0 0 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 0 0 0 0 0 0;1 1 0 0 0 0 0;",
+ "0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 0 0 0 0 0 0;0 1 1 0 0 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 0 0 0 0 0 1;1 0 0 0 0 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 0 0 0 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 0 0 0;0 1 1 1 0 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 0 0 0 0 0 1;1 1 0 0 0 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 0 0 0 0 0 1;1 1 1 0 0 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 1 0 0 0 0 0;1 1 0 0 0 0 0;",
+ "0 0 0 0 0 1 0;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 1 0 0 0 0 0;0 0 1 1 0 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 1 0 0 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 1 0 0 0 0 0;1 0 1 1 0 0 0;",
+ "0 0 0 0 0 1 0;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 0 0 0 0 0;0 0 1 1 1 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 1 0 0 0 0 1;1 1 0 0 0 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 1 0 0 0 0 1;1 0 1 0 0 1 0;",
+ "0 0 0 0 0 1 0;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 1 0 0 0 0 1;0 0 1 1 0 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 1 0 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 0 0 0 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 1 0 0 0 0 1;1 1 1 0 0 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 1 0 0 0 0 1;1 0 1 1 0 1 0;",
+ "0 0 0 0 0 1 0;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 0 0 0 0 1;0 0 1 1 1 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 0 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 1 0 0 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 1 1 0 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 1 1 0 0 0 0;1 1 0 1 0 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 1 0 0 0 0;1 0 0 1 1 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 1 1 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 1 0 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 1 1 0 0 0 1;1 1 1 0 0 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 1 1 0 0 0 1;1 1 0 1 0 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 1 0 0 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 1 1 0 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 1 0 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 1 1 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 1 1 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 1 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 1 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 0 0;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 0 0 0;0 1 0 0 0 0 0;0 0 1 0 0 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 0 0 0 0 0 0;0 1 0 0 0 0 0;1 1 0 0 0 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 0 0 0;0 1 0 0 0 0 0;1 0 1 0 0 0 0;",
+ "0 0 0 0 1 0 0;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 0 0;0 1 0 0 0 0 0;0 0 1 1 0 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 0 0 0 0 0 1;0 1 0 0 0 0 0;1 0 0 0 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 0 0 0;0 1 0 0 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 0 0;0 1 0 0 0 0 0;1 0 1 1 0 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 0 0 0 0 0 1;0 1 0 0 0 0 0;1 1 0 0 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 0 0 1;0 1 0 0 0 0 0;1 0 1 0 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 0 0;0 1 0 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 0 0 1;0 1 0 0 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 0 1;0 1 0 0 0 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 0 0 0 0 0 1;0 1 0 0 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 0 1;0 1 0 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 0 0 1;0 1 0 0 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 0 1;0 1 0 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 0 1 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 0 0 0 0 0 1;1 1 0 0 0 0 0;0 1 0 0 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 0 0 0;1 1 0 0 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 0 0;1 1 0 0 0 0 0;0 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 0 0 0 0 0 1;1 1 0 0 0 0 0;1 1 0 0 1 0 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 0 0 1;1 1 0 0 0 0 0;0 1 1 0 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 0 0 0 0 0 0;1 1 0 0 0 0 1;1 1 0 0 0 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 0 0 0;1 1 0 0 0 0 1;1 0 1 0 0 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 0 0;1 1 0 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 0 0 1;1 1 0 0 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 0 1;1 1 0 0 0 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 0 1;1 1 0 0 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 0 0 0;1 1 0 0 0 0 1;1 1 1 0 0 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 0 0;1 1 0 0 0 0 1;1 0 1 1 0 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 0 0 0 0 0 1;1 1 0 0 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 0 1;1 1 0 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 0 0;1 1 0 0 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 0 0 1;1 1 0 0 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 0 1;1 1 0 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 0;1 0 0 0 0 0 0;0 1 1 0 0 0 0;1 1 0 0 0 0 0;",
+ "0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 0 0 0 0 0 0;0 1 1 0 0 0 0;0 1 1 0 0 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 0 0 0 0 0 0;0 1 1 0 0 0 0;1 0 0 1 0 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 1 0;0 0 0 0 0 0 0;1 0 0 0 0 0 1;0 1 1 0 0 0 0;1 0 0 0 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 0 0 0 0 0 0;0 1 1 0 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 0 0 0 0 0 0;0 1 1 0 0 0 0;1 1 0 1 0 0 0;",
+ "0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 0 0 0 0 0 0;0 1 1 0 0 0 0;0 1 1 1 0 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 0;1 0 0 0 0 0 1;0 1 1 0 0 0 0;1 1 0 0 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 0 0 0 0 0 1;0 1 1 0 0 0 0;1 0 0 1 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 0;1 0 0 0 0 0 0;0 1 1 0 0 0 1;1 1 0 0 0 1 0;",
+ "0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 0 0 0 0 0 0;0 1 1 0 0 0 1;0 1 1 0 0 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 0 0 0 0 0 0;0 1 1 0 0 0 1;1 0 0 1 0 1 0;",
+ "0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 0 0 0 0 0 0;0 1 1 0 0 0 1;0 1 0 1 0 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 1 0;0 0 0 0 0 0 0;1 0 0 0 0 0 1;0 1 1 0 0 0 1;1 0 0 0 1 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 0 0 0 0 0 0;0 1 1 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 0 0 0 0 0 1;0 1 1 0 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 0 0 0 0 0 1;0 1 1 0 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 0 0 0 0 0 0;0 1 1 0 0 0 1;1 1 1 0 0 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 0 0 0 0 0 0;0 1 1 0 0 0 1;1 1 0 1 0 1 0;",
+ "0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 0 0 0 0 0 0;0 1 1 0 0 0 1;0 1 1 1 0 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 0;1 0 0 0 0 0 1;0 1 1 0 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 0 0 0 0 0 1;0 1 1 0 0 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 0 0 0 0 0 1;0 1 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 0 0 0 0 0 0;0 1 1 0 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 0 0 0 0 0 1;0 1 1 0 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 0 0 0 0 0 1;0 1 1 0 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 0 0 0 0 0 1;0 1 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 1 0;1 0 0 0 1 0 0;0 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 0 0 0 0 1 1;1 0 0 0 1 0 1;1 0 0 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 1 0;1 0 0 0 1 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;1 0 0 0 1 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 0 0 0 0 1 1;1 0 0 0 1 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;1 0 0 0 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;1 0 0 0 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;1 0 0 0 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 0 0 0 0 0 1;1 1 1 0 0 0 0;0 1 1 0 1 0 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 0 0 0 0 0 1;1 1 1 0 0 0 0;0 1 0 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 0 0 0 0 0 0;1 1 1 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 0 0 0 0 0 1;1 1 1 0 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 0 0 0 0 0 1;1 1 1 0 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 0 0 0 0 0 1;1 1 1 0 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 0 0 0 0 0 0;1 1 1 0 0 0 1;1 1 1 0 0 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 0 0 0 0 0 0;1 1 1 0 0 0 1;1 1 0 1 0 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 0;1 0 0 0 0 0 1;1 1 1 0 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 0 0 0 0 0 1;1 1 1 0 0 0 1;0 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 0;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 0 0 0 0 0 1;1 1 1 0 0 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 0 0 0 0 0 1;1 1 1 0 0 0 1;0 1 0 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 0 0 0 0 0 1;1 1 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 0 0 0 0 0 0;1 1 1 0 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 0 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 0 0 0 0 0 1;1 1 1 0 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 0 0 0 0 0 1;1 1 1 0 0 0 1;0 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 0 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 0 0 0 0 0 0;0 1 1 1 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;0 1 1 1 0 0 0;0 1 1 1 0 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 1 0;1 0 0 0 0 0 1;0 1 1 1 0 0 0;1 1 0 0 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;0 1 1 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 0 0 0 0 0 1;0 1 1 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 0 0 0 0 0 0;0 1 1 1 0 0 1;1 1 1 0 0 1 0;",
+ "0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;0 1 1 1 0 0 1;0 1 1 1 0 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 1 0;1 0 0 0 0 0 1;0 1 1 1 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;0 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 0 0 0 0 0 1;0 1 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 1 0;1 1 0 0 1 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;1 1 0 0 1 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;1 1 0 0 1 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 0 0 0 0 1 1;1 1 0 0 1 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;1 1 0 0 1 0 1;1 0 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;1 1 0 0 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 1 0;1 1 0 0 1 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;1 1 0 0 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;1 1 0 0 1 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;1 1 0 0 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;1 1 1 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 0 0 0 0 0 1;1 1 1 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 1 1 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 0 0 0 0 0 1;1 1 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 1 1 0 0 1;0 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;1 1 1 0 1 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 0 0 0 0 1 1;1 1 1 0 1 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;1 1 1 0 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;1 1 1 0 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 0 0 0 0 1 1;1 1 1 0 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;1 1 1 0 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;1 1 1 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 1 0 0 0 0 0;1 1 0 0 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 1 0 0 0 0 1;1 1 0 0 0 0 0;1 1 0 0 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 0 0 0 0 0;1 1 0 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 1 0 0 0 0 1;1 1 0 0 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 1 0 0 0 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 1 0 0 0 0 1;1 1 0 0 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 1 0 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 1 0 0 0 0 1;1 1 0 0 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 1 0 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 1 0 0 0 0 0;1 0 1 0 0 0 0;0 1 1 0 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 1 0 0 0 0 0;1 0 1 0 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 0 0;1 0 1 0 0 0 0;0 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 1 0 0 0 0 1;1 0 1 0 0 0 0;1 0 1 0 1 0 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 1 0 0 0 0 1;1 0 1 0 0 0 0;0 1 1 0 1 0 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 0 1 0 0 0 0;0 0 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 0;0 0 0 0 0 1 0;0 0 0 0 0 0 0;1 1 0 0 0 0 1;1 0 1 0 0 0 1;1 0 0 0 1 1 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 0 0;1 1 0 0 0 0 1;1 0 1 0 0 0 1;0 1 0 0 1 1 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 0 0;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 0 1 0 0 0 1;0 0 0 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 0 0;1 0 1 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 1 0 0 0 0 1;1 0 1 0 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 0 1 0 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 0 1 0 0 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 0 1 0 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 0 0;1 1 0 0 0 0 1;1 0 1 0 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 1 0 0 0 0 1;1 0 1 0 0 0 1;0 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 0;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 0 1 0 0 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 0 1 0 0 0 1;0 1 0 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 0 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 1 0 0 0 0 1;1 0 1 0 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 0 1 0 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 0 1 0 0 0 1;0 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 0 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 1 0;1 1 0 0 0 0 0;0 0 1 1 0 0 0;1 1 0 0 0 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 1 0 0 0 0 0;0 0 1 1 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 1 0;1 1 0 0 0 0 1;0 0 1 1 0 0 0;1 1 0 0 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 1 0 0 0 0 1;0 0 1 1 0 0 0;1 0 1 0 1 0 0;",
+ "0 0 0 0 1 0 0;0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 1;0 0 1 1 0 0 0;0 0 1 1 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 0;0 0 0 0 0 1 0;0 0 0 0 0 1 0;1 1 0 0 0 0 1;0 0 1 1 0 0 1;1 0 0 0 1 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 0;0 0 1 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 1 0 0 0 0 1;0 0 1 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 1;0 0 1 1 0 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 1 0;1 1 0 0 0 0 1;0 0 1 1 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 1 0 0 0 0 1;0 0 1 1 0 0 1;1 0 1 0 1 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 1;0 0 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 1 0 0 0 0 1;0 0 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 1;0 0 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 1 0 0 0 0 0;1 1 1 0 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 1 0 0 0 0 0;1 1 1 0 0 0 0;1 1 0 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 0;1 1 0 0 0 0 1;1 1 1 0 0 0 0;1 1 0 0 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 0 0;1 1 1 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 1 0 0 0 0 1;1 1 1 0 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 1 1 0 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 1 1 0 0 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 1 0 0 0 0 0;1 1 1 0 0 0 1;1 1 1 0 0 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 1 0 0 0 0 0;1 1 1 0 0 0 1;1 1 0 1 0 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 1 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 0 0;1 1 1 0 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 1 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 1 1 0 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 1 1 0 0 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 0;1 0 1 1 0 0 0;0 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 1 0 0 0 0 1;1 0 1 1 0 0 0;0 1 1 0 1 0 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 1;1 0 1 1 0 0 0;0 0 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 0;1 0 1 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 1 0 0 0 0 1;1 0 1 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 1;1 0 1 1 0 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 1;1 0 1 1 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 1 0 0 0 0 0;1 0 1 1 0 0 1;1 1 1 0 0 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 0;1 0 1 1 0 0 1;1 0 1 1 0 1 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 0;1 0 1 1 0 0 1;0 1 1 1 0 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 1 0;1 1 0 0 0 0 1;1 0 1 1 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 1;1 0 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 0;1 0 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 1 0 0 0 0 1;1 0 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 1;1 0 1 1 0 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 1;1 0 1 1 0 0 1;0 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 1;1 0 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 0 0 0 1 0;1 1 0 0 1 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 1 0 0 0 1 1;1 1 0 0 1 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 1 0 0 0 1 1;1 1 0 0 1 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 0 0 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 1 0 0 0 1 1;1 1 0 0 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 0 0 1 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 0 0 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 1 0;1 0 1 0 1 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 1 0 0 0 1 1;1 0 1 0 1 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 1 0 0 0 1 1;1 0 1 0 1 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 1 1;1 0 1 0 1 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 1 1;1 0 1 0 1 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 0 0;1 1 0 0 0 1 1;1 0 1 0 1 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 1 0 0 0 1 1;1 0 1 0 1 0 1;0 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 0;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 1 0 0 0 1 1;1 0 1 0 1 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 1 1;1 0 1 0 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 1 0 0 0 1 1;1 0 1 0 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 1 0 0 0 1 1;1 0 1 0 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 1 1;1 0 1 0 1 0 1;0 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 1 1;1 0 1 0 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 1 0;0 0 1 1 1 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 1 0 0 0 1 1;0 0 1 1 1 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 1 1;0 0 1 1 1 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 1 0;1 1 0 0 0 1 1;0 0 1 1 1 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 1 0 0 0 1 1;0 0 1 1 1 0 1;1 0 1 0 1 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 1 1;0 0 1 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 1 0 0 0 1 1;0 0 1 1 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 1 1;0 0 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 0;1 1 1 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 1 0;1 1 1 0 1 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 1 0 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 1 0;1 1 1 0 1 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 1 0 0 0 1 1;1 1 1 0 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 1 0 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 1 0 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 1 0;1 0 1 1 1 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 1 1;1 0 1 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 1 0;1 0 1 1 1 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 1 0 0 0 1 1;1 0 1 1 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 1 1;1 0 1 1 1 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 1 1;1 0 1 1 1 0 1;0 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 1 1;1 0 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 1 1;1 1 1 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 1 0 0 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 1 1 0 0 0 0;1 1 1 0 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 1 1 0 0 0 0;1 1 1 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 1 1 0 0 0 1;1 1 1 0 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 0 0;1 1 1 0 0 0 1;1 1 1 0 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 0;0 0 0 0 1 1 0;0 0 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 0 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 1 1 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 1 1 0 0 0 0;1 1 0 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;1 1 1 0 0 0 1;1 1 0 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 0;0 0 0 0 0 1 1;1 1 1 0 0 0 1;1 1 0 1 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 1 1 0 0 0 1;1 1 0 1 0 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 0;0 0 0 0 0 1 0;1 1 1 0 0 0 1;1 1 0 1 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 0;1 1 1 0 0 0 1;1 1 0 1 0 0 1;1 0 1 0 1 1 0;",
+ "0 0 0 0 1 1 0;0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 1 1 0 0 0 1;1 1 0 1 0 0 1;0 0 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 1 1 0 0 0 1;1 1 0 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;1 1 1 0 0 0 1;1 1 0 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 1 1 0 0 0 1;1 1 0 1 0 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 1 1 0 0 0 1;1 1 0 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 1 1 0 0 0 0;1 1 1 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 1 1 0 0 0 1;1 1 1 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 1 1 0 0 0 1;1 1 1 1 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 1 1 0 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 1 1 0 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 1 1 0 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 1 1 0 0 1 1;1 1 1 0 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 1 1 0 0 1 1;1 1 1 0 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 0 1;1 1 1 0 0 1 1;1 1 1 0 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 1 1 0 0 1 1;1 1 1 0 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 1 1 0 0 1 1;1 1 0 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 1 1 0 0 1 1;1 1 0 1 1 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 1 1 0 0 1 1;1 1 0 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 1 1 0 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 1 1 1 0 0 0;1 1 1 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 1 1 1 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;1 1 1 1 0 0 1;1 1 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 1 1 1 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 1 1 1 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;1 0 0 0 0 0 0;0 1 0 0 0 0 0;0 0 1 0 0 0 0;1 1 0 0 0 0 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 0 0;0 0 0 0 0 1 0;1 0 0 0 0 0 1;0 1 0 0 0 0 0;0 0 1 0 0 0 0;1 0 0 1 0 0 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;0 1 0 0 0 0 0;0 0 1 0 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;1 0 0 0 0 0 1;0 1 0 0 0 0 0;0 0 1 0 0 0 0;1 1 0 1 0 0 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 0;0 0 1 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;1 0 0 0 0 0 1;0 1 0 0 0 0 1;0 0 1 0 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 1;0 0 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 1;0 0 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;0 1 0 0 0 0 0;1 0 1 0 0 0 0;0 1 1 0 0 0 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 0 0;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 0;1 0 1 0 0 0 0;0 0 1 1 0 0 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 0;1 0 0 0 0 0 0;0 1 0 0 0 0 1;1 0 1 0 0 0 0;0 1 0 0 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;0 1 0 0 0 0 0;1 0 1 0 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;1 0 0 0 0 0 1;0 1 0 0 0 0 0;1 0 1 0 0 0 0;1 1 0 1 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 0;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 0;1 0 1 0 0 0 0;1 0 1 1 0 0 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 0;1 0 1 0 0 0 0;0 1 1 1 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;1 0 0 0 0 0 0;0 1 0 0 0 0 1;1 0 1 0 0 0 0;1 1 0 0 1 0 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;0 1 0 0 0 0 1;1 0 1 0 0 0 0;0 1 1 0 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;1 0 0 0 0 0 0;0 1 0 0 0 0 0;1 0 1 0 0 0 1;1 1 0 0 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 0;1 0 1 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;0 1 0 0 0 0 1;1 0 1 0 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;1 0 0 0 0 0 1;0 1 0 0 0 0 1;1 0 1 0 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 1;1 0 1 0 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;0 1 0 0 0 0 0;1 0 1 0 0 0 1;1 1 1 0 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 0;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 0;1 0 1 0 0 0 1;1 0 1 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;1 0 0 0 0 0 0;0 1 0 0 0 0 1;1 0 1 0 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 1;1 0 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 0;1 0 1 0 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;0 1 0 0 0 0 1;1 0 1 0 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 1;1 0 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 0 1;0 0 0 0 0 0 0;1 0 0 0 0 1 0;0 1 0 0 0 0 1;1 0 0 1 0 0 0;0 1 0 0 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;0 1 0 0 0 0 0;1 0 0 1 0 0 0;1 1 0 1 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 0;0 0 0 0 0 0 1;1 0 0 0 0 1 1;0 1 0 0 0 0 0;1 0 0 1 0 0 0;1 0 1 1 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 0 0;1 0 0 0 0 1 0;0 1 0 0 0 0 1;1 0 0 1 0 0 0;1 1 0 0 1 0 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 0 1;0 0 0 0 0 0 1;1 0 0 0 0 1 0;0 1 0 0 0 0 1;1 0 0 1 0 0 0;0 1 1 0 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 0;0 0 0 0 0 0 0;1 0 0 0 0 1 1;0 1 0 0 0 0 0;1 0 0 1 0 0 1;1 0 0 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;0 1 0 0 0 0 0;1 0 0 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 0 1;1 0 0 0 0 1 0;0 1 0 0 0 0 1;1 0 0 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 0 0 1 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;0 1 0 0 0 0 0;1 0 0 1 0 0 1;1 1 0 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 0;0 0 0 0 0 0 1;1 0 0 0 0 1 1;0 1 0 0 0 0 0;1 0 0 1 0 0 1;1 0 1 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 0 0 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;0 1 0 0 0 0 0;1 0 0 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 0 0 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 0 0 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 1;1 1 1 0 0 0 0;0 0 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 0;1 1 1 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 0 0 1;0 1 0 0 0 0 1;1 1 1 0 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 1;1 1 1 0 0 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;0 1 0 0 0 0 0;1 1 1 0 0 0 1;1 1 1 0 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 0 0 1;0 1 0 0 0 0 0;1 1 1 0 0 0 1;1 1 0 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 0;1 1 1 0 0 0 1;1 0 1 1 0 1 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 0;1 1 1 0 0 0 1;0 1 1 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 0 1 0;1 0 0 0 0 0 1;0 1 0 0 0 0 1;1 1 1 0 0 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 1;1 1 1 0 0 0 1;0 0 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 1;1 1 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 0;1 1 1 0 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 0 0 1;0 1 0 0 0 0 1;1 1 1 0 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 1;1 1 1 0 0 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;0 1 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 0;0 1 0 0 0 0 0;1 1 0 1 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 0 1 0;0 1 0 0 0 0 1;1 1 0 1 0 0 0;1 1 0 0 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;0 1 0 0 0 0 0;1 1 0 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 0;0 1 0 0 0 0 1;1 1 0 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 1 0 1 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 0 0 1;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 1 0 1 0 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;0 1 0 0 0 0 0;1 1 0 1 0 0 1;1 1 0 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 0 1 0;0 1 0 0 0 0 1;1 1 0 1 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 0 0 0;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 1 0 1 0 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 1 0 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;0 1 0 0 0 0 0;1 1 0 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 0;0 1 0 0 0 0 1;1 1 0 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 1 0 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 0 0 1;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 1 0 1 0 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 1 0 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 1 0;0 1 0 0 0 0 1;1 0 1 1 0 0 0;0 1 1 0 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;0 1 0 0 0 0 0;1 0 1 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 1 0;0 1 0 0 0 0 1;1 0 1 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 0 1 1 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;1 0 0 0 0 1 1;0 1 0 0 0 0 0;1 0 1 1 0 0 1;1 1 0 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 0;0 0 0 0 0 1 1;1 0 0 0 0 1 1;0 1 0 0 0 0 0;1 0 1 1 0 0 1;1 0 1 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;1 0 0 0 0 1 0;0 1 0 0 0 0 1;1 0 1 1 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 1 0;0 1 0 0 0 0 1;1 0 1 1 0 0 1;0 1 1 0 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 0 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;0 1 0 0 0 0 0;1 0 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 1 0;0 1 0 0 0 0 1;1 0 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 0 1 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 0 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;0 1 0 0 0 0 0;1 1 1 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 0;0 1 0 0 0 0 1;1 1 1 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 1 1 1 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 1 1 1 0 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;0 1 0 0 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 0;0 1 0 0 0 0 1;1 1 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 1 1 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 1 1 1 0 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;0 1 0 0 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;0 1 0 0 0 1 1;1 1 0 1 1 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;0 1 0 0 0 1 1;1 1 0 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;0 1 0 0 0 1 1;1 1 0 1 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;0 1 0 0 0 1 1;1 1 0 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;0 1 0 0 0 1 1;1 1 1 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;0 1 0 0 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 0 0;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 0 0;0 1 1 0 0 0 0;0 0 1 1 0 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;1 1 0 0 0 0 0;0 1 1 0 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 0 0 1;1 1 0 0 0 0 0;0 1 1 0 0 0 0;1 1 0 1 0 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 0 0;0 1 1 0 0 0 0;1 0 1 1 0 0 0;",
+ "0 0 0 1 1 0 0;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 0 0;0 1 1 0 0 0 0;0 1 1 1 0 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 0 0 0;1 1 0 0 0 0 1;0 1 1 0 0 0 0;1 1 0 0 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 0 0 0;1 1 0 0 0 0 1;0 1 1 0 0 0 0;1 0 1 0 1 0 0;",
+ "0 0 0 1 1 0 0;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;1 1 0 0 0 0 1;0 1 1 0 0 0 0;0 1 1 0 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 0 0 0;1 1 0 0 0 0 0;0 1 1 0 0 0 1;1 1 0 0 0 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 0 0 0;1 1 0 0 0 0 0;0 1 1 0 0 0 1;1 0 1 0 0 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 0 0;0 1 1 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;1 1 0 0 0 0 1;0 1 1 0 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 0 0 1;1 1 0 0 0 0 1;0 1 1 0 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 0 1;0 1 1 0 0 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 1 1 0 0;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 0 1;0 1 1 0 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;1 1 0 0 0 0 0;0 1 1 0 0 0 1;1 1 1 0 0 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 0 0 1;1 1 0 0 0 0 0;0 1 1 0 0 0 1;1 1 0 1 0 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 0 0;0 1 1 0 0 0 1;1 0 1 1 0 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 0 0 0;1 1 0 0 0 0 1;0 1 1 0 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 0 1;0 1 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 0 0;0 1 1 0 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;1 1 0 0 0 0 1;0 1 1 0 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 0 1;0 1 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 0;1 1 0 0 0 0 0;0 1 0 1 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;1 1 0 0 0 0 0;0 1 0 1 0 0 0;1 1 0 1 0 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 0 1 0;1 1 0 0 0 0 1;0 1 0 1 0 0 0;1 1 0 0 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;1 1 0 0 0 0 0;0 1 0 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 0;1 1 0 0 0 0 1;0 1 0 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;1 1 0 0 0 0 1;0 1 0 1 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;1 1 0 0 0 0 1;0 1 0 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;1 1 0 0 0 0 1;0 1 0 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;1 1 0 0 0 0 1;0 1 0 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 0 0;1 1 1 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;1 1 0 0 0 0 1;1 1 1 0 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 0 0 1;1 1 0 0 0 0 1;1 1 1 0 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 0 1;1 1 1 0 0 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 0 1;1 1 1 0 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 0 0 0;1 1 0 0 0 0 1;1 1 1 0 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 0 1;1 1 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 0 0;1 1 1 0 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;1 1 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;1 1 0 0 0 0 1;1 1 0 1 0 0 0;0 1 0 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;1 1 0 0 0 0 1;1 1 0 1 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;1 1 0 0 0 0 1;1 1 0 1 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 0;1 1 0 0 0 0 0;1 1 0 1 0 0 1;1 1 1 0 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 0 1 0;1 1 0 0 0 0 1;1 1 0 1 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;1 1 0 0 0 0 1;1 1 0 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;1 1 0 0 0 0 0;1 1 0 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 0;1 1 0 0 0 0 1;1 1 0 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;1 1 0 0 0 0 1;1 1 0 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;1 1 0 0 0 0 1;1 1 0 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 0;1 1 0 0 0 0 0;0 1 1 1 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 0 1 0;1 1 0 0 0 0 1;0 1 1 1 0 0 0;1 0 1 0 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;1 1 0 0 0 0 0;0 1 1 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 0;1 1 0 0 0 0 1;0 1 1 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 0 1 1;1 1 0 0 0 0 1;0 1 1 1 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 0 1 1;1 1 0 0 0 0 0;0 1 1 1 0 0 1;1 1 0 1 0 1 0;",
+ "0 0 0 1 1 0 0;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;1 1 0 0 0 0 0;0 1 1 1 0 0 1;0 1 1 1 0 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;1 1 0 0 0 0 1;0 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;1 1 0 0 0 0 0;0 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 0;1 1 0 0 0 0 1;0 1 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 0 1 1;1 1 0 0 0 0 1;0 1 1 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;1 1 0 0 0 0 1;0 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 0 0 1 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 0 0 0;1 1 0 0 0 1 1;1 1 0 0 1 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 0 1;1 0 0 0 0 0 0;1 1 0 0 0 1 1;1 1 0 0 1 0 1;1 0 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 0 0;1 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 0 0 1 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 0 0 1 0 1;0 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 0;0 0 0 0 0 0 1;1 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 0 0 1 0 1;0 0 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 0 0 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 0 0;1 1 0 0 0 1 1;1 1 0 0 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 0 0 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 0 1;1 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 0 0 1 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 0 0 1 0 1;0 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 0 0 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 1 0;1 0 1 0 1 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 1 1;1 0 1 0 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;1 1 0 0 0 1 1;1 0 1 0 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 1 1;1 0 1 0 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;1 1 0 0 0 0 1;1 1 1 1 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;1 1 0 0 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;1 1 0 0 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 0 1 1;1 1 0 0 0 0 1;1 1 1 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;1 1 0 0 0 0 1;1 1 1 1 0 0 1;0 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;1 1 0 0 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 1 0 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 1 0;1 1 1 0 1 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 0;1 1 0 0 0 1 1;1 1 1 0 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 1 0 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 1 0 1 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 1 0 1 0 1;0 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 1 0 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;1 1 0 0 0 1 1;1 1 0 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 0 1 1;1 1 0 0 0 1 1;1 1 0 1 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 0 1 1;1 1 0 0 0 1 1;1 1 0 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;1 1 0 0 0 1 1;1 1 1 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 0 1 1;1 1 0 0 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 0;0 1 1 0 0 0 0;0 1 1 0 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;1 0 0 0 0 0 1;0 1 1 0 0 0 0;0 1 1 0 0 0 0;1 1 0 1 0 0 0;",
+ "0 0 0 1 0 0 0;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 0;0 1 1 0 0 0 1;0 1 1 0 0 0 0;0 1 1 0 1 0 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;0 1 1 0 0 0 0;0 1 1 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 0;0 1 1 0 0 0 1;0 1 1 0 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;1 0 0 0 0 0 1;0 1 1 0 0 0 1;0 1 1 0 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 0 0 0;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 0;0 1 1 0 0 0 1;0 1 1 0 0 0 1;0 1 1 0 1 1 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;0 1 1 0 0 0 1;0 1 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 0;0 1 1 0 0 0 1;0 1 1 0 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;0 1 1 0 0 0 1;0 1 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 0;0 1 1 0 0 0 0;1 1 1 0 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;0 1 1 0 0 0 0;1 1 1 0 0 0 0;0 1 1 1 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;1 0 0 0 0 0 0;0 1 1 0 0 0 1;1 1 1 0 0 0 0;1 1 0 0 1 0 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 0;0 1 1 0 0 0 1;1 1 1 0 0 0 0;0 1 1 0 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 1 1 0;1 0 0 0 0 0 1;0 1 1 0 0 0 1;1 1 1 0 0 0 0;1 0 0 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;0 1 1 0 0 0 0;1 1 1 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 0;0 1 1 0 0 0 1;1 1 1 0 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;1 0 0 0 0 0 1;0 1 1 0 0 0 1;1 1 1 0 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;0 1 1 0 0 0 1;1 1 1 0 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 0;0 1 1 0 0 0 0;1 1 1 0 0 0 1;1 1 1 0 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;1 0 0 0 0 0 1;0 1 1 0 0 0 0;1 1 1 0 0 0 1;1 1 0 1 0 1 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;0 1 1 0 0 0 0;1 1 1 0 0 0 1;0 1 1 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;0 1 1 0 0 0 1;1 1 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;0 1 1 0 0 0 0;1 1 1 0 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 0;0 1 1 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;1 0 0 0 0 0 1;0 1 1 0 0 0 1;1 1 1 0 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;0 1 1 0 0 0 1;1 1 1 0 0 0 1;0 1 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;0 1 1 0 0 0 1;1 1 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 1 0;0 1 1 0 0 0 0;1 1 0 1 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 0;1 0 0 0 0 1 0;0 1 1 0 0 0 1;1 1 0 1 0 0 0;1 1 0 0 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 1 0 1;1 0 0 0 0 1 0;0 1 1 0 0 0 1;1 1 0 1 0 0 0;1 0 1 0 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 1 0 0;1 0 0 0 0 1 1;0 1 1 0 0 0 1;1 1 0 1 0 0 0;1 0 0 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 1 1;0 1 1 0 0 0 0;1 1 0 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 1 0;0 1 1 0 0 0 1;1 1 0 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 0;1 0 0 0 0 1 1;0 1 1 0 0 0 1;1 1 0 1 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 1 0 1;1 0 0 0 0 1 1;0 1 1 0 0 0 1;1 1 0 1 0 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 0;1 0 0 0 0 1 1;0 1 1 0 0 0 0;1 1 0 1 0 0 1;1 1 0 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 1 0 1;1 0 0 0 0 1 1;0 1 1 0 0 0 0;1 1 0 1 0 0 1;1 0 1 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 1 1;0 1 1 0 0 0 1;1 1 0 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 1 1;0 1 1 0 0 0 0;1 1 0 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 1 0;0 1 1 0 0 0 1;1 1 0 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 0;1 0 0 0 0 1 1;0 1 1 0 0 0 1;1 1 0 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 1 0 1;1 0 0 0 0 1 1;0 1 1 0 0 0 1;1 1 0 1 0 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 1 1;0 1 1 0 0 0 1;1 1 0 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 0 1;0 1 1 0 0 1 0;1 1 0 0 1 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 0;1 0 0 0 0 0 1;0 1 1 0 0 1 1;1 1 0 0 1 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 1 0 1;1 0 0 0 0 0 1;0 1 1 0 0 1 1;1 1 0 0 1 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 0 1;0 1 1 0 0 1 1;1 1 0 0 1 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 0 0;0 1 1 0 0 1 0;1 1 0 0 1 0 1;1 1 1 0 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 0;1 0 0 0 0 0 1;0 1 1 0 0 1 0;1 1 0 0 1 0 1;1 1 0 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 1 0 1;1 0 0 0 0 0 1;0 1 1 0 0 1 0;1 1 0 0 1 0 1;1 0 1 1 0 1 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 0 1;0 1 1 0 0 1 0;1 1 0 0 1 0 1;0 1 1 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 0;1 0 0 0 0 0 0;0 1 1 0 0 1 1;1 1 0 0 1 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 1 0 1;1 0 0 0 0 0 0;0 1 1 0 0 1 1;1 1 0 0 1 0 1;1 0 1 0 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 1 0 0;1 0 0 0 0 0 1;0 1 1 0 0 1 1;1 1 0 0 1 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 1 1;0 0 0 0 1 0 0;1 0 0 0 0 0 1;0 1 1 0 0 1 1;1 1 0 0 1 0 1;0 1 0 1 1 1 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 1 0;0 0 0 0 1 0 1;1 0 0 0 0 0 1;0 1 1 0 0 1 1;1 1 0 0 1 0 1;0 0 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 0 1;0 1 1 0 0 1 1;1 1 0 0 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 0 1;0 1 1 0 0 1 0;1 1 0 0 1 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 0 0;0 1 1 0 0 1 1;1 1 0 0 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 0;1 0 0 0 0 0 1;0 1 1 0 0 1 1;1 1 0 0 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 1 0 1;1 0 0 0 0 0 1;0 1 1 0 0 1 1;1 1 0 0 1 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 0 1;0 1 1 0 0 1 1;1 1 0 0 1 0 1;0 1 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 0 1;0 1 1 0 0 1 1;1 1 0 0 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;0 1 1 0 0 1 0;0 1 1 0 1 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 0;0 1 1 0 0 1 1;0 1 1 0 1 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;1 0 0 0 0 0 1;0 1 1 0 0 1 1;0 1 1 0 1 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;1 0 0 0 0 0 0;0 1 1 0 0 1 1;0 1 1 0 1 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 1 0 0 0;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 0;0 1 1 0 0 1 1;0 1 1 0 1 0 1;0 1 1 0 1 1 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 1 0;0 0 0 0 1 1 0;1 0 0 0 0 0 1;0 1 1 0 0 1 1;0 1 1 0 1 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;0 1 1 0 0 1 1;0 1 1 0 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 0;0 1 1 0 0 1 1;0 1 1 0 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;1 0 0 0 0 0 1;0 1 1 0 0 1 1;0 1 1 0 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 0 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;0 1 1 0 0 1 1;0 1 1 0 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 1 0 1;1 0 0 0 0 1 1;0 1 1 0 0 1 0;1 0 0 1 1 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 1 0 1;1 0 0 0 0 1 0;0 1 1 0 0 1 1;1 0 0 1 1 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 1 0 0;1 0 0 0 0 1 1;0 1 1 0 0 1 1;1 0 0 1 1 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 1 0 1;1 0 0 0 0 1 0;0 1 1 0 0 1 0;1 0 0 1 1 0 1;1 1 1 0 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 1 0 0;1 0 0 0 0 1 1;0 1 1 0 0 1 0;1 0 0 1 1 0 1;1 1 0 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 0;0 0 0 0 1 0 0;1 0 0 0 0 1 1;0 1 1 0 0 1 1;1 0 0 1 1 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 1 0 1;1 0 0 0 0 1 1;0 1 1 0 0 1 1;1 0 0 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 1 0 1;1 0 0 0 0 1 1;0 1 1 0 0 1 0;1 0 0 1 1 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 1 0 1;1 0 0 0 0 1 0;0 1 1 0 0 1 1;1 0 0 1 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 1 0 0;1 0 0 0 0 1 1;0 1 1 0 0 1 1;1 0 0 1 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 0 1;0 0 0 0 1 0 1;1 0 0 0 0 1 1;0 1 1 0 0 1 1;1 0 0 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 1 1;0 1 1 0 0 0 0;1 1 1 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 1 1;0 1 1 0 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 1 1;0 1 1 0 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 1 1;0 1 1 0 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;0 1 1 0 0 1 0;1 1 1 0 1 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;0 1 1 0 0 1 1;1 1 1 0 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;0 1 1 0 0 1 0;1 1 1 0 1 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 0;0 1 1 0 0 1 1;1 1 1 0 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;1 0 0 0 0 0 1;0 1 1 0 0 1 1;1 1 1 0 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 0 1 0;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;0 1 1 0 0 1 1;1 1 1 0 1 0 1;0 1 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;0 1 1 0 0 1 1;1 1 1 0 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 1 1;0 1 1 0 0 1 0;1 1 0 1 1 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 1 1;0 1 1 0 0 1 1;1 1 0 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 1 1;0 1 1 0 0 1 0;1 1 0 1 1 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 0;1 0 0 0 0 1 1;0 1 1 0 0 1 1;1 1 0 1 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 0;0 0 0 0 1 0 1;1 0 0 0 0 1 1;0 1 1 0 0 1 1;1 1 0 1 1 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 1 1;0 1 1 0 0 1 1;1 1 0 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 1 1;0 1 1 0 0 1 1;1 1 1 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 0 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 1 1;0 1 1 0 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 1 0 0;1 0 0 1 0 0 0;0 1 1 0 0 0 0;0 1 1 0 0 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 0 0 0 1 0 1;1 0 0 1 0 0 0;0 1 1 0 0 0 0;1 1 0 1 0 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 1 0;1 0 0 0 1 0 1;1 0 0 1 0 0 1;0 1 1 0 0 0 0;1 0 0 1 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 0 0 0 1 0 0;1 0 0 1 0 0 0;0 1 1 0 0 0 1;1 1 0 0 0 1 0;",
+ "0 0 0 1 1 0 0;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 1 0 0;1 0 0 1 0 0 0;0 1 1 0 0 0 1;0 1 1 0 0 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 1 0;1 0 0 0 1 0 1;1 0 0 1 0 0 0;0 1 1 0 0 0 1;1 0 0 1 0 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 1 0 1;1 0 0 1 0 0 0;0 1 1 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 0 0 0 1 0 1;1 0 0 1 0 0 1;0 1 1 0 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 1 0 0;1 0 0 1 0 0 0;0 1 1 0 0 0 1;1 1 1 0 0 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 0 0 0 1 0 1;1 0 0 1 0 0 0;0 1 1 0 0 0 1;1 1 0 1 0 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 0 1 0;0 0 0 0 0 1 0;1 0 0 0 1 0 1;1 0 0 1 0 0 1;0 1 1 0 0 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 1 0 1;1 0 0 1 0 0 1;0 1 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 1 0 1;1 0 0 1 0 0 0;0 1 1 0 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 0 0 0 1 0 1;1 0 0 1 0 0 1;0 1 1 0 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 1 0 1;1 0 0 1 0 0 1;0 1 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 0 0 0;0 0 0 0 0 0 0;1 0 0 0 1 1 1;1 0 0 1 0 1 1;1 0 0 1 1 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 1 1 1;1 0 0 1 0 1 1;1 0 0 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 0;1 0 0 0 1 1 1;1 0 0 1 0 1 1;1 0 0 1 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 0 0 1;0 0 0 0 0 0 1;1 0 0 0 1 1 1;1 0 0 1 0 1 1;1 0 0 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 1 1 1;1 0 0 1 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 1 1 1;1 0 0 1 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 1 1 0;1 0 0 1 0 0 1;1 1 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 0;1 0 0 0 1 1 1;1 0 0 1 0 0 1;1 1 1 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 1 1 1;1 0 0 1 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 0 0 0 1 1 1;1 0 0 1 0 1 1;1 1 0 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 0;1 0 0 0 1 1 1;1 0 0 1 0 1 1;1 1 0 1 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 0 1 0;0 0 0 0 0 0 1;1 0 0 0 1 1 1;1 0 0 1 0 1 1;1 1 0 1 1 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 0 1;1 0 0 0 1 1 1;1 0 0 1 0 1 1;1 1 0 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 1 1 1;1 0 0 1 0 1 1;1 1 1 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 0 1 1;0 0 0 0 0 1 1;1 0 0 0 1 1 1;1 0 0 1 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;1 1 1 0 0 0 0;1 1 1 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 0;1 1 1 0 0 0 1;1 1 1 0 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;1 0 0 0 0 0 0;1 1 1 0 0 0 1;1 1 1 0 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 0;1 1 1 0 0 0 1;1 1 1 0 0 0 1;0 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 0;0 0 0 0 1 1 0;1 0 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 0 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 1 1 0;1 0 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 0 0 1;0 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 0;1 1 1 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;1 0 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 0 0 1;0 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 1 0;1 1 1 0 0 0 0;0 1 1 1 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 1 1;1 1 1 0 0 0 0;0 1 1 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 1 0;1 1 1 0 0 0 1;0 1 1 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;1 0 0 0 0 1 0;1 1 1 0 0 0 1;0 1 1 1 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 1 1 0 0;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 1 0;1 1 1 0 0 0 1;0 1 1 1 0 0 1;0 1 1 0 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 0;0 0 0 0 1 1 0;1 0 0 0 0 1 1;1 1 1 0 0 0 1;0 1 1 1 0 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 1 1;1 1 1 0 0 0 1;0 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 1 0;1 1 1 0 0 0 1;0 1 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;1 0 0 0 0 1 1;1 1 1 0 0 0 1;0 1 1 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 1 1;1 1 1 0 0 0 1;0 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 1 1;1 1 1 0 0 0 1;1 1 1 1 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 1 1;1 1 1 0 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 1 1;1 1 1 0 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 1 1;1 1 1 0 0 0 1;1 1 1 1 0 0 1;0 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 1 1;1 1 1 0 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;1 1 1 0 0 1 1;1 1 1 0 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 0;1 1 1 0 0 1 1;1 1 1 0 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;1 0 0 0 0 0 1;1 1 1 0 0 1 1;1 1 1 0 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;1 1 1 0 0 1 1;1 1 1 0 1 0 1;0 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 0 1;1 1 1 0 0 1 1;1 1 1 0 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 1 1;1 1 1 0 0 1 1;1 1 0 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 1 1;1 1 1 0 0 1 0;1 1 0 1 1 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 0;1 0 0 0 0 1 1;1 1 1 0 0 1 1;1 1 0 1 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 0;0 0 0 0 1 0 1;1 0 0 0 0 1 1;1 1 1 0 0 1 1;1 1 0 1 1 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 0 1;1 0 0 0 0 1 1;1 1 1 0 0 1 1;1 1 0 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;1 0 0 0 0 1 1;1 1 1 0 0 1 1;0 1 1 1 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 1 1;1 1 1 0 0 1 1;0 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 0 1 1;1 1 1 0 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 1 0 0;1 1 0 1 0 0 1;1 1 1 0 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 1 0 1;1 1 0 1 0 0 1;1 1 1 0 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 1 0 1;1 1 0 1 0 0 1;1 1 1 0 0 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 1 0 1;1 1 0 1 0 0 1;1 1 1 0 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 1 0 0;1 1 0 1 0 0 0;1 1 1 0 0 0 1;1 1 1 0 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 1 0 1;1 1 0 1 0 0 0;1 1 1 0 0 0 1;1 1 0 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 1 0 1;1 1 0 1 0 0 0;1 1 1 0 0 0 1;1 0 1 1 0 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 1 0 1;1 1 0 1 0 0 0;1 1 1 0 0 0 1;0 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 1 0 0;1 1 0 1 0 0 1;1 1 1 0 0 0 1;1 0 1 0 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 1 0 0;1 1 0 1 0 0 1;1 1 1 0 0 0 1;0 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 1 0;1 0 0 0 1 0 1;1 1 0 1 0 0 1;1 1 1 0 0 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 1 0 1;1 1 0 1 0 0 1;1 1 1 0 0 0 1;0 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 1 0 1;1 1 0 1 0 0 1;1 1 1 0 0 0 1;0 0 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 1 0 1;1 1 0 1 0 0 1;1 1 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 1 0 1;1 1 0 1 0 0 0;1 1 1 0 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 1 0 0;1 1 0 1 0 0 1;1 1 1 0 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 0;1 0 0 0 1 0 1;1 1 0 1 0 0 1;1 1 1 0 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 1 0 1;1 1 0 1 0 0 1;1 1 1 0 0 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 1 0 1;1 1 0 1 0 0 1;1 1 1 0 0 0 1;0 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 1 0 1;1 1 0 1 0 0 1;1 1 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 1 1 1;1 1 0 1 0 0 1;1 1 0 1 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 1 1 0;1 1 0 1 0 0 1;1 1 0 1 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 1 1 0;1 1 0 1 0 0 1;1 1 0 1 0 0 1;0 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 0 0;1 0 0 0 1 1 1;1 1 0 1 0 0 1;1 1 0 1 0 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 1 1 1;1 1 0 1 0 0 1;1 1 0 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 1 1 0;1 1 0 1 0 0 1;1 1 0 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 1 1 1;1 1 0 1 0 0 1;1 1 0 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 0 1;1 0 0 0 1 1 1;1 1 0 1 0 0 1;1 1 0 1 0 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 1 1 1;1 1 0 1 0 0 1;1 1 0 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 0 0;0 0 0 0 0 1 1;1 0 0 0 1 1 1;1 1 0 1 0 0 1;1 0 1 1 0 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;1 0 0 0 1 1 0;1 1 0 1 0 0 1;1 0 1 1 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 1 1 0;1 1 0 1 0 0 1;1 0 1 1 0 0 1;0 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 0 0;0 0 0 0 0 1 0;1 0 0 0 1 1 1;1 1 0 1 0 0 1;1 0 1 1 0 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 1 1 1;1 1 0 1 0 0 1;1 0 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 1 1 0;1 1 0 1 0 0 1;1 0 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 0;1 0 0 0 1 1 1;1 1 0 1 0 0 1;1 0 1 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 1 1 1;1 1 0 1 0 0 1;1 0 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 1 1 1;1 1 0 1 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 1 1 1;1 1 0 1 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 1 1;1 0 0 0 1 1 1;1 1 0 1 0 0 1;1 1 1 1 0 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 1 1 1;1 1 0 1 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 1 1 1;1 1 0 1 0 1 1;1 1 0 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 0;1 0 0 0 1 1 1;1 1 0 1 0 1 1;1 1 0 1 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 0;0 0 0 0 0 0 1;1 0 0 0 1 1 1;1 1 0 1 0 1 1;1 1 0 1 1 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 0 1;1 0 0 0 1 1 1;1 1 0 1 0 1 1;1 1 0 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 0 1;0 0 0 0 0 1 1;1 0 0 0 1 1 1;1 1 0 1 0 1 1;1 0 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 0 1 1;1 0 0 0 1 1 1;1 1 0 1 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 1 1 1;1 1 1 1 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 1 1 0;1 1 1 1 0 0 1;1 1 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 0;1 0 0 0 1 1 1;1 1 1 1 0 0 1;1 1 1 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 1 1 1;1 1 1 1 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 0 1 1 1;0 0 0 0 1 1 1;1 0 0 0 1 1 1;1 1 1 1 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 0 1 1;1 1 0 0 0 1 1;1 1 0 0 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 0 1 1;1 1 0 0 0 1 1;1 1 0 0 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 0 1 1;1 1 0 0 0 1 0;1 1 0 0 0 0 1;1 1 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 0 1 0;1 1 0 0 0 1 1;1 1 0 0 0 0 1;1 1 1 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 0;0 0 0 0 0 1 1;1 1 0 0 0 1 1;1 1 0 0 0 0 1;1 1 1 1 0 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 0 1 1;1 1 0 0 0 1 1;1 1 0 0 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 0 0 0;1 1 0 0 0 1 1;1 1 0 0 0 1 1;1 1 0 1 1 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 0 0 0 1 1;1 1 0 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 0 0 0;1 1 0 0 0 1 1;1 1 0 0 0 1 1;1 1 0 1 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 0 0 1;1 1 0 0 0 1 1;1 1 0 0 0 1 1;1 1 0 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 0 1 1;1 1 0 0 0 1 1;1 1 0 0 0 1 1;1 1 1 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 0 1 1;1 1 0 0 0 1 1;1 1 0 0 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 0 0;1 0 1 0 0 0 0;0 1 1 0 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 0;1 1 0 0 0 0 1;1 0 1 0 0 0 0;0 1 1 0 0 0 0;1 1 0 1 0 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 0;0 0 0 0 1 1 1;1 1 0 0 0 0 1;1 0 1 0 0 0 0;0 1 1 0 0 0 0;1 0 1 1 0 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 0 1;1 0 1 0 0 0 0;0 1 1 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 0;1 1 0 0 0 0 1;1 0 1 0 0 0 1;0 1 1 0 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 1 0 0;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 0 1;1 0 1 0 0 0 1;0 1 1 0 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 0 1;1 0 1 0 0 0 1;0 1 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 0 1;1 0 1 0 0 0 1;0 1 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 0 1;1 0 1 0 0 0 0;1 1 1 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 0;1 1 0 0 0 0 1;1 0 1 0 0 0 1;1 1 1 0 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 1 1 0;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 0 1;1 0 1 0 0 0 1;1 1 1 0 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 0 0;1 0 1 0 0 0 0;1 1 1 0 0 0 1;1 1 1 0 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 0;1 1 0 0 0 0 1;1 0 1 0 0 0 0;1 1 1 0 0 0 1;1 1 0 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 0;0 0 0 0 1 1 1;1 1 0 0 0 0 1;1 0 1 0 0 0 0;1 1 1 0 0 0 1;1 0 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 0 1;1 0 1 0 0 0 1;1 1 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 0 1;1 0 1 0 0 0 0;1 1 1 0 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 0 1;1 0 1 0 0 0 1;1 1 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 1 0 0 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 0 1;1 0 1 1 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 0 0;0 0 0 0 1 1 1;1 1 0 0 0 1 0;1 0 1 0 0 0 1;1 0 1 1 0 0 1;1 0 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 0 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 0 1;1 0 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 0 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 0 0;1 0 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 0 1;0 0 0 0 1 1 1;1 1 0 0 0 1 0;1 0 1 0 0 0 1;1 0 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 0 0;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 0 1;1 0 1 1 0 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 0 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 0 1;1 0 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 0;1 0 1 0 0 0 0;0 1 1 1 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 0;1 1 0 0 0 1 0;1 0 1 0 0 0 1;0 1 1 1 0 0 0;1 1 0 0 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 0;1 0 1 0 0 0 1;0 1 1 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 0;1 1 0 0 0 1 1;1 0 1 0 0 0 1;0 1 1 1 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 0;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 0 1;0 1 1 1 0 0 0;1 0 1 1 1 0 0;",
+ "0 0 0 1 1 0 0;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 0 1;0 1 1 1 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 0;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 0 0;0 1 1 1 0 0 1;1 0 1 1 0 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 0 1;0 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 0 0;0 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 0;1 1 0 0 0 1 1;1 0 1 0 0 0 1;0 1 1 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 0;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 0 1;0 1 1 1 0 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 0 1;0 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 0;1 0 1 0 0 0 1;1 1 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 0;1 1 0 0 0 1 1;1 0 1 0 0 0 1;1 1 1 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 0;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 0 1;1 1 1 1 0 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 0 1;1 1 1 1 0 0 1;0 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 0 1;1 1 0 0 0 1 1;1 0 1 0 0 1 1;1 1 0 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 0 0;1 1 0 0 0 1 1;1 0 1 0 0 1 1;1 1 0 1 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 0;0 0 0 0 1 0 1;1 1 0 0 0 1 1;1 0 1 0 0 1 1;1 1 0 1 1 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 0 1;1 1 0 0 0 1 1;1 0 1 0 0 1 1;1 1 0 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 0;1 1 0 0 0 1 1;1 0 1 0 0 1 1;0 1 1 1 1 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 1 0 0;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 1 1;0 1 1 1 1 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 1 1;0 1 1 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 0;1 1 0 0 0 1 1;1 0 1 0 0 1 1;0 1 1 1 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 0 0;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 1 1;0 1 1 1 1 0 1;0 1 1 1 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 1 1;0 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 1 1;1 1 1 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 0 1 0 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 1 1;1 1 0 0 0 0 1;1 1 1 0 0 0 0;1 1 1 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 1 1;1 1 0 0 0 0 0;1 1 1 0 0 0 1;1 1 1 0 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 1 0;1 1 0 0 0 0 0;1 1 1 0 0 0 1;1 1 1 0 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 1 1;1 1 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 1 1;1 1 0 0 0 0 0;1 1 1 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 1 1;1 1 0 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 0 1;1 1 0 0 0 1 1;1 1 1 0 0 0 0;1 1 0 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 0 1;1 1 0 0 0 1 0;1 1 1 0 0 0 1;1 1 0 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 0 0;1 1 0 0 0 1 1;1 1 1 0 0 0 1;1 1 0 1 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 0 0;1 1 0 0 0 1 1;1 1 1 0 0 0 0;1 1 0 1 0 0 1;1 1 0 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 0;0 0 0 0 1 0 1;1 1 0 0 0 1 1;1 1 1 0 0 0 0;1 1 0 1 0 0 1;1 0 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 0;0 0 0 0 1 0 0;1 1 0 0 0 1 1;1 1 1 0 0 0 1;1 1 0 1 0 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 1 1 1 0;0 0 0 0 1 0 1;1 1 0 0 0 1 1;1 1 1 0 0 0 1;1 1 0 1 0 0 1;0 0 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 0 1;1 1 0 0 0 1 1;1 1 1 0 0 0 1;1 1 0 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 0 1;1 1 0 0 0 1 1;1 1 1 0 0 0 0;1 1 0 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 0 0;1 1 0 0 0 1 1;1 1 1 0 0 0 1;1 1 0 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 0;0 0 0 0 1 0 1;1 1 0 0 0 1 1;1 1 1 0 0 0 1;1 1 0 1 0 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 0 1;1 1 0 0 0 1 1;1 1 1 0 0 0 1;1 1 0 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 0;1 1 1 0 0 0 1;1 1 1 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 1 1 0 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 1 1 0 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 1 1 0 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 1 1;1 1 0 0 0 0 1;1 1 1 0 0 1 1;1 1 1 0 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 1 1;1 1 0 0 0 0 0;1 1 1 0 0 1 1;1 1 1 0 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 1 1;1 1 0 0 0 0 1;1 1 1 0 0 1 1;1 1 1 0 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 1 1;1 1 0 0 0 1 1;1 1 1 0 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 0 1 1;1 1 0 0 1 1 1;1 1 0 1 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 0 1 1;1 1 0 0 1 1 1;1 1 0 1 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 0 1 1;1 1 0 0 1 1 0;1 1 0 1 0 0 1;1 1 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 0 1 1;1 1 0 0 1 1 1;1 1 0 1 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 0 0 1;1 1 0 0 1 1 1;1 1 0 1 0 1 1;1 1 0 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 0 0 0;1 1 0 0 1 1 1;1 1 0 1 0 1 1;1 1 0 1 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 0;0 0 0 0 0 0 1;1 1 0 0 1 1 1;1 1 0 1 0 1 1;1 1 0 1 1 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 0 0 1;1 1 0 0 1 1 1;1 1 0 1 0 1 1;1 1 0 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 0 1;0 0 0 0 0 1 1;1 1 0 0 1 1 1;1 1 0 1 0 1 1;1 0 1 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 0 1;0 0 0 0 0 1 1;1 1 0 0 1 1 1;1 1 0 1 0 1 0;1 0 1 1 1 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 0 0;0 0 0 0 0 1 1;1 1 0 0 1 1 1;1 1 0 1 0 1 1;1 0 1 1 1 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 0 1;0 0 0 0 0 1 1;1 1 0 0 1 1 1;1 1 0 1 0 1 1;1 0 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 0 1 1;1 1 0 0 1 1 1;1 1 0 1 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 1 0 1;1 0 1 1 0 0 0;1 1 1 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 1 1 0;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 1 0 1;1 0 1 1 0 0 1;1 1 1 0 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 0;0 0 0 0 1 1 1;1 1 0 0 1 0 1;1 0 1 1 0 0 0;1 1 1 0 0 0 1;1 0 1 1 0 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 1 0 1;1 0 1 1 0 0 0;1 1 1 0 0 0 1;0 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 0;0 0 0 0 1 1 0;1 1 0 0 1 0 1;1 0 1 1 0 0 1;1 1 1 0 0 0 1;1 0 0 1 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 1 0 1 1;0 0 0 0 1 1 0;1 1 0 0 1 0 1;1 0 1 1 0 0 1;1 1 1 0 0 0 1;0 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 1 0 1;1 0 1 1 0 0 1;1 1 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 1 0 1;1 0 1 1 0 0 0;1 1 1 0 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 0;1 1 0 0 1 0 1;1 0 1 1 0 0 1;1 1 1 0 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 0;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 1 0 1;1 0 1 1 0 0 1;1 1 1 0 0 0 1;0 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 1 0 1;1 0 1 1 0 0 1;1 1 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 1 1 1;1 0 1 1 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 1 1 1;1 0 1 1 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 1 1 0;1 0 1 1 0 0 1;1 1 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 1 1 1;1 0 1 1 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 1 1 1;1 0 1 1 0 1 1;0 1 1 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 0;1 1 0 0 1 1 1;1 0 1 1 0 1 1;0 1 1 1 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 0 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 1 1 1;1 0 1 1 0 1 1;0 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 0 1 1;0 0 0 0 1 1 1;1 1 0 0 1 1 1;1 0 1 1 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 1 1;1 1 0 0 1 1 1;1 1 1 1 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 1 1;1 1 0 0 1 1 0;1 1 1 1 0 0 1;1 1 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 1 0;1 1 0 0 1 1 1;1 1 1 1 0 0 1;1 1 1 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 0;0 0 0 0 1 1 1;1 1 0 0 1 1 1;1 1 1 1 0 0 1;1 1 1 1 0 0 1;1 0 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 1 1;1 1 0 0 1 1 1;1 1 1 1 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 0 1 1 1;1 1 0 0 1 1 1;1 1 1 1 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 1 1 1 1;1 1 1 0 0 0 1;1 1 1 0 0 0 0;1 1 1 0 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 1 1 1 0;1 1 1 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 1 1 1 1;1 1 1 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 1 1 1 1;1 1 1 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 1 1 1 1;1 1 1 0 0 1 0;1 1 1 0 0 0 1;1 1 1 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 1 1 1 1;1 1 1 0 0 1 1;1 1 1 0 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 1 1 1 1;1 1 1 0 0 1 1;1 1 1 0 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 1 1 1 1;1 1 1 0 0 1 1;1 1 1 0 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 1 1 1 1;1 1 1 0 0 1 1;1 1 1 0 0 1 1;1 1 1 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 1 1 1 1;1 1 1 0 0 1 1;1 1 1 0 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 0 1 1 1 1;0 0 0 1 1 1 1;0 0 0 1 1 1 1;1 1 1 0 1 1 1;1 1 1 1 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 0 1 0 1;0 0 0 1 0 1 1;1 0 0 0 1 0 1;0 1 0 0 0 1 0;1 0 1 0 0 0 0;0 1 0 1 0 0 0;1 1 1 0 0 0 0;",
+ "0 0 1 0 1 0 1;0 0 0 1 0 1 0;1 0 0 0 1 0 1;0 1 0 0 0 1 0;1 0 1 0 0 0 1;0 1 0 1 0 0 0;1 0 1 0 1 0 0;",
+ "0 0 1 0 1 0 1;0 0 0 1 0 1 1;1 0 0 0 1 0 1;0 1 0 0 0 1 1;1 0 1 0 0 0 0;0 1 0 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 1 0 1 0 1;0 0 0 1 0 1 1;1 0 0 0 1 0 1;0 1 0 0 0 1 0;1 0 1 0 0 0 1;0 1 0 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 1 0 1 0 1;0 0 0 1 0 1 1;1 0 0 0 1 0 1;0 1 0 0 0 1 1;1 0 1 0 0 0 1;0 1 0 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 0 1 0 1;0 0 0 1 0 1 1;1 0 0 0 1 0 1;0 1 0 0 0 1 1;1 0 1 0 0 0 1;0 1 0 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 0;1 0 0 0 1 1 1;0 1 0 0 0 0 1;1 0 1 0 0 0 1;1 1 1 0 0 0 0;1 0 1 1 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 1 1;0 1 0 0 0 0 0;1 0 1 0 0 0 0;1 1 1 0 0 0 1;1 1 1 0 0 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 0;1 0 0 0 1 1 1;0 1 0 0 0 0 1;1 0 1 0 0 0 0;1 1 1 0 0 0 1;1 0 1 1 0 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 1 1;0 1 0 0 0 0 1;1 0 1 0 0 0 1;1 1 1 0 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 1 1;0 1 0 0 0 0 1;1 0 1 0 0 0 0;1 1 1 0 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 1 1;0 1 0 0 0 0 0;1 0 1 0 0 0 1;1 1 1 0 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 1 0;0 1 0 0 0 0 1;1 0 1 0 0 0 1;1 1 1 0 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 0;1 0 0 0 1 1 1;0 1 0 0 0 0 1;1 0 1 0 0 0 1;1 1 1 0 0 0 1;1 0 1 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 1 1;0 1 0 0 0 0 1;1 0 1 0 0 0 1;1 1 1 0 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 0 1 1 0;0 0 0 1 0 1 1;1 0 0 0 1 0 1;0 1 0 0 0 1 1;1 0 1 0 0 0 0;1 1 0 1 0 0 0;0 1 1 1 0 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 0 1;0 1 0 0 0 1 1;1 0 1 0 0 0 0;1 1 0 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 0 1;0 1 0 0 0 1 0;1 0 1 0 0 0 1;1 1 0 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 1 0 1 1 0;0 0 0 1 0 1 1;1 0 0 0 1 0 1;0 1 0 0 0 1 1;1 0 1 0 0 0 1;1 1 0 1 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 0 1;0 1 0 0 0 1 0;1 0 1 0 0 0 0;1 1 0 1 0 0 1;1 1 1 0 0 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 0 0;0 1 0 0 0 1 1;1 0 1 0 0 0 0;1 1 0 1 0 0 1;1 1 0 1 0 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 0 1;0 1 0 0 0 1 1;1 0 1 0 0 0 1;1 1 0 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 0 1;0 1 0 0 0 1 1;1 0 1 0 0 0 0;1 1 0 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 0 1;0 1 0 0 0 1 1;1 0 1 0 0 0 1;1 1 0 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 0 1;1 0 0 0 1 1 1;0 1 0 0 0 0 0;1 0 1 0 0 1 1;1 0 1 0 1 0 0;1 1 1 0 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 0 0;1 0 0 0 1 1 1;0 1 0 0 0 0 0;1 0 1 0 0 1 1;1 0 1 0 1 0 1;1 0 1 0 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 0 1;1 0 0 0 1 1 1;0 1 0 0 0 0 1;1 0 1 0 0 1 1;1 0 1 0 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 0 1;1 0 0 0 1 1 1;0 1 0 0 0 0 0;1 0 1 0 0 1 1;1 0 1 0 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 0 1;1 0 0 0 1 1 1;0 1 0 0 0 0 1;1 0 1 0 0 1 1;1 0 1 0 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 1 1;0 1 0 0 0 1 1;1 0 1 0 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 1 1;0 1 0 0 0 1 1;1 0 1 0 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 1 1;0 1 0 0 0 1 0;1 0 1 0 0 0 1;1 1 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 1 0;0 1 0 0 0 1 1;1 0 1 0 0 0 1;1 1 1 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 1 1;0 1 0 0 0 1 1;1 0 1 0 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 1 1;0 1 0 0 0 0 1;1 0 1 0 0 1 1;1 1 1 0 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 1 1;0 1 0 0 0 0 0;1 0 1 0 0 1 1;1 1 1 0 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 0;1 0 0 0 1 1 1;0 1 0 0 0 0 1;1 0 1 0 0 1 1;1 1 1 0 1 0 1;1 0 1 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 1 1;0 1 0 0 0 0 1;1 0 1 0 0 1 1;1 1 1 0 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 1 1;0 1 0 0 0 1 1;1 0 1 0 0 1 1;1 1 1 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 0 1 1;1 0 0 0 1 1 1;0 1 0 0 0 1 1;1 0 1 0 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 0 1;0 1 0 0 0 1 1;1 1 1 0 0 0 0;1 1 0 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 0 1;0 1 0 0 0 1 0;1 1 1 0 0 0 1;1 1 0 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 0 0;0 1 0 0 0 1 1;1 1 1 0 0 0 1;1 1 0 1 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 0;1 0 0 0 1 0 1;0 1 0 0 0 1 1;1 1 1 0 0 0 1;1 1 0 1 0 0 0;1 0 1 1 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 0 0;0 1 0 0 0 1 0;1 1 1 0 0 0 1;1 1 0 1 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 0 1;0 1 0 0 0 1 1;1 1 1 0 0 0 1;1 1 0 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 0 1;0 1 0 0 0 1 0;1 1 1 0 0 0 1;1 1 0 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 0 1;0 1 0 0 0 1 1;1 1 1 0 0 0 1;1 1 0 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 0 1;1 0 0 0 1 1 1;0 1 0 0 0 1 1;1 1 1 0 0 0 0;1 0 1 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 0 1;1 0 0 0 1 1 1;0 1 0 0 0 1 0;1 1 1 0 0 0 1;1 0 1 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 0 1;1 0 0 0 1 1 0;0 1 0 0 0 1 1;1 1 1 0 0 0 1;1 0 1 1 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 0 1;1 0 0 0 1 1 0;0 1 0 0 0 1 0;1 1 1 0 0 0 1;1 0 1 1 0 0 1;1 1 0 0 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 0 0;1 0 0 0 1 1 1;0 1 0 0 0 1 0;1 1 1 0 0 0 1;1 0 1 1 0 0 1;1 0 1 0 1 1 0;",
+ "0 0 1 0 1 1 0;0 0 0 1 1 0 1;1 0 0 0 1 1 0;0 1 0 0 0 1 1;1 1 1 0 0 0 1;1 0 1 1 0 0 1;0 1 0 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 0 1;1 0 0 0 1 1 1;0 1 0 0 0 1 1;1 1 1 0 0 0 1;1 0 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 0 1;1 0 0 0 1 1 1;0 1 0 0 0 1 0;1 1 1 0 0 0 1;1 0 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 0 1;1 0 0 0 1 1 0;0 1 0 0 0 1 1;1 1 1 0 0 0 1;1 0 1 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 0 1;1 0 0 0 1 1 1;0 1 0 0 0 1 1;1 1 1 0 0 0 1;1 0 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;0 1 0 0 0 1 0;1 1 1 0 0 0 1;1 1 1 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 0;0 1 0 0 0 1 1;1 1 1 0 0 0 1;1 1 1 1 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;0 1 0 0 0 1 1;1 1 1 0 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;0 1 0 0 0 1 1;1 1 1 0 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;0 1 0 0 0 1 1;1 1 1 0 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;0 1 0 0 0 0 1;1 1 1 0 0 1 1;1 1 1 0 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;0 1 0 0 0 0 0;1 1 1 0 0 1 1;1 1 1 0 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 0;0 1 0 0 0 0 1;1 1 1 0 0 1 1;1 1 1 0 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 0;1 0 0 0 1 1 1;0 1 0 0 0 0 1;1 1 1 0 0 1 1;1 1 1 0 1 0 1;1 0 1 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;0 1 0 0 0 0 1;1 1 1 0 0 1 1;1 1 1 0 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 0 1;0 1 0 0 0 1 1;1 1 1 0 0 1 1;1 1 0 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 0 1;0 1 0 0 0 1 1;1 1 1 0 0 1 1;1 1 0 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 0 1;1 0 0 0 1 1 1;0 1 0 0 0 1 1;1 1 1 0 0 1 1;1 0 1 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 0 1;1 0 0 0 1 1 0;0 1 0 0 0 1 1;1 1 1 0 0 1 1;1 0 1 1 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 0 1;1 0 0 0 1 1 1;0 1 0 0 0 1 1;1 1 1 0 0 1 1;1 0 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;0 1 0 0 0 1 1;1 1 1 0 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;0 1 0 0 1 1 1;1 1 1 1 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;0 1 0 0 1 1 0;1 1 1 1 0 0 1;1 1 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;0 1 0 0 1 1 1;1 1 1 1 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 0 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;0 1 0 0 1 1 1;1 1 1 1 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 0 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;1 1 0 0 0 1 1;0 1 1 0 0 0 0;1 1 1 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 1 1 0 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;1 1 0 0 0 1 1;0 1 1 0 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 1 0 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;1 1 0 0 0 1 1;0 1 1 0 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 1 1 0 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;1 1 0 0 0 1 0;0 1 1 0 0 0 1;1 1 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 1 1 0 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 0;1 1 0 0 0 1 1;0 1 1 0 0 0 1;1 1 1 1 0 0 1;1 1 0 1 1 1 0;",
+ "0 0 1 1 0 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;1 1 0 0 0 1 1;0 1 1 0 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 0 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;1 1 0 0 0 1 1;0 1 1 0 0 1 1;1 1 1 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 1 0 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;1 1 0 0 0 1 1;0 1 1 0 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 1 0 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;1 1 0 0 0 1 1;1 1 1 0 0 0 0;0 1 1 1 0 0 0;1 1 1 1 0 0 0;",
+ "0 0 1 1 1 0 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;1 1 0 0 0 1 0;1 1 1 0 0 0 1;0 1 1 1 0 0 0;1 1 1 0 1 0 0;",
+ "0 0 1 1 1 0 1;0 0 0 1 1 1 1;1 0 0 0 1 1 0;1 1 0 0 0 1 1;1 1 1 0 0 0 1;0 1 1 1 0 0 0;1 1 0 1 1 0 0;",
+ "0 0 1 1 1 0 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;1 1 0 0 0 1 1;1 1 1 0 0 0 1;0 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 1 1 0 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;1 1 0 0 0 1 1;1 1 1 0 0 0 1;0 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 1 1 0;0 0 0 1 1 1 1;1 0 0 0 1 1 1;1 1 0 0 0 1 1;1 1 1 0 0 0 1;1 1 1 1 0 0 0;0 1 1 1 1 0 0;",
+ "0 0 1 1 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;1 1 0 0 0 1 1;1 1 1 0 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 1 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;1 1 0 0 0 1 0;1 1 1 0 0 0 1;1 1 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;1 1 0 0 0 1 1;1 1 1 0 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 0 1;1 1 0 0 0 1 1;1 1 1 0 0 1 1;1 1 0 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 1 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 0 0;1 1 0 0 0 1 1;1 1 1 0 0 1 1;1 1 0 1 1 0 1;1 1 0 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 0 1;1 1 0 0 0 1 1;1 1 1 0 0 1 1;1 1 0 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;1 1 0 0 0 1 1;1 1 1 0 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;1 1 0 0 1 1 1;1 1 1 1 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 1 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;1 1 0 0 1 1 0;1 1 1 1 0 0 1;1 1 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;1 1 0 0 1 1 1;1 1 1 1 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 0 1 1 1 1;1 0 0 0 1 1 1;1 1 0 0 1 1 1;1 1 1 1 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 0 0 1 1 1;1 0 0 1 1 1 1;1 0 1 0 0 1 1;1 1 1 0 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 1 1 1 1;0 0 0 0 1 1 1;1 0 0 1 1 1 1;1 0 1 0 0 1 1;1 1 1 0 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 1 1 1 1 1;0 0 0 0 1 1 1;1 0 0 1 1 1 1;1 0 1 0 0 1 0;1 1 1 0 0 0 1;1 1 1 1 0 0 1;1 1 1 0 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 0 0 1 1 1;1 0 0 1 1 1 1;1 0 1 0 0 1 1;1 1 1 0 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 0 0 1 1 1;1 0 0 1 1 1 1;1 0 1 0 0 0 1;1 1 1 0 0 1 1;1 1 1 0 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 0 0 1 1 1;1 0 0 1 1 1 1;1 0 1 0 0 1 1;1 1 1 0 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 0 0 0 1 1;1 0 0 1 1 1 1;1 0 1 0 1 1 1;1 0 1 1 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 1 1 1 1;0 0 0 0 0 1 1;1 0 0 1 1 1 1;1 0 1 0 1 1 1;1 0 1 1 0 0 0;1 1 1 1 0 0 1;1 1 1 1 0 1 0;",
+ "0 0 1 1 1 1 1;0 0 0 0 0 1 1;1 0 0 1 1 1 1;1 0 1 0 1 1 1;1 0 1 1 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 0 0 0 0 1;1 0 0 1 1 1 1;1 0 1 0 1 1 1;1 0 1 1 0 1 1;1 0 1 1 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 1 1 1 1;0 0 0 0 0 0 0;1 0 0 1 1 1 1;1 0 1 0 1 1 1;1 0 1 1 0 1 1;1 0 1 1 1 0 1;1 0 1 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 0 0 0 0 1;1 0 0 1 1 1 1;1 0 1 0 1 1 1;1 0 1 1 0 1 1;1 0 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 0 0 0 1 1;1 0 0 1 1 1 1;1 0 1 0 1 1 1;1 0 1 1 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 0 0 1 1 1;1 0 0 1 1 1 1;1 0 1 0 1 1 1;1 1 1 1 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 0 1 1 1 1;1 0 0 1 1 1 1;1 1 1 0 1 1 1;1 1 1 1 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 1 1 1 1 1;1 1 0 0 1 1 1;1 1 0 0 0 0 1;1 1 1 0 0 1 1;1 1 1 0 1 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 1 1 1 1;0 0 1 1 1 1 1;1 1 0 0 1 1 1;1 1 0 0 0 0 0;1 1 1 0 0 1 1;1 1 1 0 1 0 1;1 1 1 0 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 1 1 1 1 0;1 1 0 0 1 1 1;1 1 0 0 0 0 1;1 1 1 0 0 1 1;1 1 1 0 1 0 1;1 0 1 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 1 1 1 1 1;1 1 0 0 1 1 1;1 1 0 0 0 0 1;1 1 1 0 0 1 1;1 1 1 0 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 1 1 1 0 1;1 1 0 0 1 1 1;1 1 0 0 0 1 1;1 1 1 0 0 1 1;1 0 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 1 1 1 1 1;1 1 0 0 1 1 1;1 1 0 0 0 1 1;1 1 1 0 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 1 1 1 1 1;1 1 0 0 1 1 1;1 1 0 0 1 1 1;1 1 1 1 0 0 1;1 1 1 1 0 0 0;1 1 1 1 1 0 0;",
+ "0 0 1 1 1 1 1;0 0 1 1 1 1 1;1 1 0 0 1 1 1;1 1 0 0 1 1 1;1 1 1 1 0 0 1;1 1 1 1 0 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 1 1 1 1 1;1 1 0 0 1 1 1;1 1 0 0 1 1 1;1 1 1 1 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 0 1 1 1 1 1;0 0 1 1 1 1 1;1 1 0 1 1 1 1;1 1 1 0 1 1 1;1 1 1 1 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;",
+ "0 1 1 1 1 1 1;1 0 1 1 1 1 1;1 1 0 1 1 1 1;1 1 1 0 1 1 1;1 1 1 1 0 1 1;1 1 1 1 1 0 1;1 1 1 1 1 1 0;";
diff --git a/macros/math/PGnauGraphtheory.pl b/macros/math/PGnauGraphtheory.pl
new file mode 100644
index 0000000000..0e6a06f09f
--- /dev/null
+++ b/macros/math/PGnauGraphtheory.pl
@@ -0,0 +1,1719 @@
+# PGgraphtheory.pl
+
+loadMacros("PGnauGraphics.pl",);
+
+##################################################### Nandor
+
+######################################
+#Name: GRmatrix_graph
+#Input:
+#Output:
+######################################
+sub GRmatrix_graph {
+ my $graph = pop @_;
+ $graph =~ s/\A\s*//;
+ $graph =~ s/;\s*\Z//;
+ my @m = split /\s*[;]\s*/, $graph;
+ my $size = scalar @m;
+ my @matrix = ();
+ my ($i, $j, @r);
+ for ($i = 0; $i < $size; $i++) {
+ @r = split /\s+/, $m[$i];
+ for ($j = 0; $j < $size; $j++) {
+ $matrix[$i][$j] = $r[$j];
+ }
+ }
+ @matrix;
+}
+
+######################################
+#Name: GRgraph_matrix
+#Input:
+#Output:
+######################################
+sub GRgraph_matrix {
+ my @matrix = @_;
+ my $size = scalar @matrix;
+ my $graph = '';
+ my ($i, $j);
+ for ($i = 0; $i < $size; $i++) {
+ for ($j = 0; $j < $size; $j++) {
+ $graph .= " $matrix[$i][$j]";
+ }
+ $graph .= ";";
+ }
+ $graph;
+}
+
+######################################
+#Name: GRshuffledgraph_graph
+#Input:
+#Output:
+######################################
+sub GRshuffledgraph_graph {
+ my $graph = pop @_;
+ my @matrix = &GRmatrix_graph($graph);
+ my ($i, $j, $size, @shmatrix);
+ $size = scalar @matrix;
+ @perm = shuffle($size);
+ # @invperm inverted_shuffle(@perm);
+ for ($i = 0; $i < $size; $i++) {
+ for ($j = 0; $j < $size; $j++) {
+ $shmatrix[ $perm[$i] ][ $perm[$j] ] = $matrix[$i][$j];
+ }
+ }
+ $graph = &GRgraph_matrix(@shmatrix);
+}
+
+######################################
+#Name: GRgraph_size_random
+#Input:
+#Output:
+######################################
+sub GRgraph_size_random {
+ my ($size, $prob) = @_;
+ my ($i, $j, $ra, @matrix);
+ for ($i = 0; $i < $size; $i++) {
+ $matrix[$i][$i] = 0;
+ for ($j = 0; $j < $i; $j++) {
+ $ra = random(0, 1, .05);
+ if ($ra <= $prob) {
+ $matrix[$i][$j] = 1;
+ $matrix[$j][$i] = 1;
+ } else {
+ $matrix[$i][$j] = 0;
+ $matrix[$j][$i] = 0;
+ }
+ }
+ }
+ &GRgraph_matrix(@matrix);
+}
+
+######################################
+#Name: GRgraph_size_random_weight_dweight
+#Input:
+#Output:
+######################################
+sub GRgraph_size_random_weight_dweight {
+ my ($size, $prob, $weight, $dweight) = @_;
+ my ($i, $j, $ra, @matrix);
+ for ($i = 0; $i < $size; $i++) {
+ $matrix[$i][$i] = 0;
+ for ($j = 0; $j < $i; $j++) {
+ $ra = random(0, 1, .05);
+ if ($ra <= $prob) {
+ $matrix[$i][$j] = $weight;
+ $matrix[$j][$i] = $weight;
+ $weight += $dweight;
+ } else {
+ $matrix[$i][$j] = 0;
+ $matrix[$j][$i] = 0;
+ }
+ }
+ }
+ &GRgraph_matrix(@matrix);
+}
+
+######################################
+#Name: GRlabel_vertex_labels
+#Input:
+#Output:
+######################################
+sub GRlabel_vertex_labels {
+ my ($vertex, $labels) = @_;
+ substr $labels, $vertex, 1;
+}
+
+######################################
+#Name: GRlabels_vertices_labels
+#Input:
+#Output:
+######################################
+sub GRlabels_vertices_labels {
+ my $labels = pop @_;
+ my @vertices = @_;
+ my $result = '';
+ my $a;
+ foreach $a (@vertices) {
+ $result .= GRlabel_vertex_labels($a, $labels) . ' ';
+ }
+ $result;
+}
+
+######################################
+#Name: GRvertex_label_labels
+#Input:
+#Output:
+######################################
+sub GRvertex_label_labels {
+ my ($label, $labels) = @_;
+ index $labels, $label;
+}
+
+######################################
+#Name: GRvertices_labels_size
+#Input:
+#Output:
+######################################
+sub GRvertices_labels_size {
+ my ($labels, $size) = @_;
+ my ($i, $result);
+ $result = "";
+ for ($i = 0; $i < $size; $i++) {
+ $result .= (substr $labels, $i, 1) . ", ";
+ }
+ if (length $result > 2) {
+ chop $result;
+ chop $result;
+ }
+ "{" . $result . "}";
+}
+
+######################################
+#Name: GRedges_graph
+#Input:
+#Output:
+######################################
+sub GRedges_graph {
+ my ($graph) = @_;
+ my @matrix = &GRmatrix_graph($graph);
+
+}
+
+######################################
+#Name: GRedgesstr_graph_labels
+#Input:
+#Output:
+######################################
+sub GRedgesstr_graph_labels {
+ my $labels = pop @_;
+ my $graph = pop @_;
+ my @matrix = &GRmatrix_graph($graph);
+ my ($i, $j, $size, $result, $li, $lj);
+ $result = "";
+ $size = scalar @matrix;
+ for ($i = 0; $i < $size; $i++) {
+ for ($j = $i + 1; $j < $size; $j++) {
+ if ($matrix[$i][$j] == 1) {
+ $li = substr $labels, $i, 1;
+ $lj = substr $labels, $j, 1;
+ $result .= "{$li,$lj}, ";
+ }
+ }
+ }
+ if (length $result > 2) {
+ chop $result;
+ chop $result;
+ }
+ "{" . $result . "}";
+}
+
+######################################
+#Name: GRdegree_graph_vertex
+#Input:
+#Output:
+######################################
+sub GRdegree_graph_vertex {
+ my $vert = pop @_;
+ my $graph = pop @_;
+ my @matrix = GRmatrix_graph($graph);
+ my ($i, $j, $size, $result);
+ $result = 0;
+ $size = scalar @matrix;
+ for ($j = 0; $j < $size; $j++) {
+ if ($matrix[$vert][$j] == 1) {
+ $result++;
+ }
+ }
+ $result;
+}
+
+######################################
+#Name: GRdegrees_graph
+#Input:
+#Output:
+######################################
+sub GRdegrees_graph {
+ my $graph = pop @_;
+ my @matrix = &GRmatrix_graph($graph);
+ my ($i, $j, $size, @result);
+ $size = scalar @matrix;
+ for ($i = 0; $i < $size; $i++) {
+ push @result, &GRdegree_graph_vertex($graph, $i);
+ }
+ @result;
+}
+
+######################################
+#Name: GRncomponents_graph
+#Input:
+#Output:
+######################################
+sub GRncomponents_graph {
+ my $graph = pop @_;
+ my @matrix = &GRmatrix_graph($graph);
+ my $size = scalar @matrix;
+ my ($i, $j, $k, $result, $con);
+ $result = $size;
+ for ($i = 0; $i < $size; $i++) {
+ $con = 0;
+ for ($j = $i + 1; $j < $size; $j++) {
+ if ($matrix[$i][$j] != 0) {
+ $con++;
+ for ($k = 0; $k < $size; $k++) {
+ $matrix[$j][$k] += $matrix[$i][$k];
+ $matrix[$k][$j] += $matrix[$k][$i];
+ }
+ }
+ }
+ if ($con > 0) {
+ $result--;
+ }
+ }
+ $result;
+}
+
+######################################
+#Name: GRpic_graph_labels
+#Input:
+#Output:
+######################################
+sub GRpic_graph_labels {
+ my $labels = pop @_;
+ my $graph = pop @_;
+ my @matrix = &GRmatrix_graph($graph);
+ my ($i, $j, $k, $lab);
+ my $pic = init_graph(-1.5, -1.5, 1.5, 1.5, 'pixels' => [ 220, 220 ]);
+ my $size = scalar @matrix;
+ my $gap = 2.0 * 3.14 / $size;
+ for ($i = 0; $i < $size; $i++) {
+ $pic->stamps(closed_circle(cos($i * $gap), sin($i * $gap), "blue"));
+ $lab = new Label(
+ 1.25 * cos($i * $gap),
+ 1.25 * sin($i * $gap),
+ &GRlabel_vertex_labels($i, $labels),
+ 'blue', 'center', 'center'
+ );
+ $pic->lb($lab);
+ }
+ for ($i = 0; $i < $size; $i++) {
+ for ($j = $i + 1; $j < $size; $j++) {
+ if ($matrix[$i][$j] != 0) {
+ $pic->moveTo(cos($i * $gap), sin($i * $gap));
+ $pic->lineTo(cos($j * $gap), sin($j * $gap), 1);
+ }
+ }
+ }
+ $pic;
+}
+
+######################################
+#Name: GRlabelpic_graph(_labels)
+#Input:
+#Output:
+######################################
+sub GRlabelpic_graph {
+ my $labels = pop @_;
+ my $graph = pop @_;
+ my @matrix = &GRmatrix_graph($graph);
+ my ($i, $j, $k, $lab);
+ my $pic = init_graph(-1.5, -1.5, 1.5, 1.5, 'pixels' => [ 300, 300 ]);
+ my $size = scalar @matrix;
+ my $gap = 2.0 * 3.14 / $size;
+ for ($i = 0; $i < $size; $i++) {
+ $pic->stamps(closed_circle(cos($i * $gap), sin($i * $gap), "blue"));
+ $lab = new Label(
+ 1.2 * cos($i * $gap),
+ 1.2 * sin($i * $gap),
+ &GRlabel_vertex_labels($i, $labels),
+ 'blue', 'center', 'center'
+ );
+ $pic->lb($lab);
+ }
+ for ($i = 0; $i < $size; $i++) {
+ for ($j = $i + 1; $j < $size; $j++) {
+ if ($matrix[$i][$j] != 0) {
+ $pic->moveTo(cos($i * $gap), sin($i * $gap));
+ $pic->lineTo(cos($j * $gap), sin($j * $gap), 9);
+ $lab = new Label(
+ .25 * cos($i * $gap) + .75 * cos($j * $gap),
+ .25 * sin($i * $gap) + .75 * sin($j * $gap),
+ $matrix[$i][$j], 'red', 'center', 'center'
+ );
+ $pic->lb($lab);
+ }
+ }
+ }
+ $pic;
+}
+
+######################################
+#Name: GRnearestnbr_graph_vertex
+#Input:
+#Output:
+######################################
+sub GRnearestnbr_graph_vertex {
+ my $origvert = pop @_;
+ my $graph = pop @_;
+ my @matrix = &GRmatrix_graph($graph);
+ my ($i, $j, $size, @path, $weight, @used, $min, $vert, $newvert);
+ $used[$origvert] = 1;
+ @path = ($origvert);
+ $weight = 0;
+ $size = scalar @matrix;
+ $vert = $origvert;
+
+ while ($size > scalar @path) {
+ $min = 0;
+ for ($i = 0; $i < $size; $i++) {
+ my $a = $matrix[$vert][$i];
+ if ($i == $vert || defined $used[$i] || $a == 0) {
+ next;
+ }
+ if ($min == 0 || $a < $min) {
+ $min = $a;
+ $newvert = $i;
+ }
+ }
+ push @path, $newvert;
+ $used[$newvert] = 1;
+ $weight += $matrix[$vert][$newvert];
+ $vert = $newvert;
+ }
+ push @path, $origvert;
+ $weight += $matrix[$vert][$origvert];
+ push @path, $weight;
+ @path;
+}
+
+######################################
+#Name: GRkruskal_graph
+#Input:
+#Output:
+######################################
+sub GRkruskal_graph {
+ my $graph2 = pop @_;
+ my $graph = $graph2;
+ my @matrix = &GRmatrix_graph($graph);
+ my $size = scalar @matrix;
+ my $ncomp = &GRncomponents_graph($graph);
+ my $tree = &GRgraph_size_random($size, -1);
+ my $ntreecomp = &GRncomponents_graph($tree);
+ my @treematrix = &GRmatrix_graph($tree);
+ my ($i, $j, @path, $weight, $treeweight);
+ $weight = 0;
+ my @weights = ();
+ my @treeweights = ();
+
+ for ($i = 0; $i < $size; $i++) {
+ for ($j = $i + 1; $j < $size; $j++) {
+ if ($matrix[$i][$j] != 0) {
+ push @weights, $matrix[$i][$j];
+ }
+ }
+ }
+ @weights = num_sort(@weights);
+ while (0 < scalar @weights) {
+ $weight = shift @weights;
+ for ($i = 0; $i < $size; $i++) {
+ for ($j = $i + 1; $j < $size; $j++) {
+ if ($matrix[$i][$j] == $weight) {
+ $matrix[$i][$j] = 0;
+ $matrix[$j][$i] = 0;
+ $treematrix[$i][$j] = $weight;
+ $treematrix[$j][$i] = $weight;
+ $tree = &GRgraph_matrix(@treematrix);
+ my $nntreecomp = &GRncomponents_graph($tree);
+ if ($nntreecomp < $ntreecomp) {
+ $ntreecomp = $nntreecomp;
+ $treeweight += $weight;
+ push @treeweights, $weight;
+ } else {
+ $treematrix[$i][$j] = 0;
+ $treematrix[$j][$i] = 0;
+ $tree = &GRgraph_matrix(@treematrix);
+ }
+ last;
+ }
+ }
+ }
+ }
+
+ unshift @treeweights, ($tree, $treeweight);
+ @treeweights;
+}
+
+######################################
+#Name: GRtex_braces
+#Input:
+#Output:
+######################################
+sub GRtex_braces {
+ my $a = shift;
+ $a =~ s/{/ $LB /g;
+ $a =~ s/}/ $RB /g;
+ $a;
+}
+
+######################################
+#Name: GRvertexlist_edgesstr_labels
+#Input:
+#Output:
+######################################
+sub GRvertexlist_edgesstr_labels {
+ my ($edgesstr, $labels) = @_;
+ $edgesstr = uc $edgesstr;
+ $edgesstr =~ s /[^$labels]//g;
+ split '', $edgesstr;
+}
+
+######################################
+#Name: GRgraph_size_labels_edgesstr
+#Input:
+#Output:
+######################################
+sub GRgraph_size_labels_edgesstr {
+ my ($size, $labels, $edgesstr) = @_;
+ my ($i, $j, $graphm);
+ # create a graph with no edges
+ for ($i = 0; $i < $size; $i++) {
+ for ($j = 0; $j < $size; $j++) {
+ $graphm[$i][$j] = 0;
+ }
+ }
+ #clean up the edges string
+ my @pairs = GRvertexlist_edgesstr_labels($edgesstr, $labels);
+ my $nedges = (scalar @pairs) / 2;
+ # erase a vertex if not even
+ if ($nedges != int($nedges)) {
+ pop @pairs;
+ $nedges = (scalar @pairs) / 2;
+ }
+ # add th edges to the empty graph
+ while (scalar @pairs) {
+ $i = shift @pairs;
+ $j = shift @pairs;
+ $i = GRvertex_label_labels($i, $labels);
+ $j = GRvertex_label_labels($j, $labels);
+ $graphm[$i][$j] = 1;
+ $graphm[$j][$i] = 1;
+ }
+ $graphin = GRgraph_matrix(@graphm);
+}
+
+####################################################################### Edgar Fisher
+
+######################################
+#Name: VertDegree
+#Input: Vertex number followed by adjacency matrix for a graph
+#Output: Degree of the indicated vertex
+######################################
+sub VertDegree {
+ my ($vert, @matrix) = @_;
+ my ($i, $size, $degree);
+
+ $size = scalar @matrix;
+ $degree = 0;
+
+ for ($i = 0; $i < $size; $i++) {
+ if ($matrix[$vert][$i] != 0) {
+ $degree++;
+ }
+ }
+
+ $degree;
+}
+
+######################################
+#Name: GRgraph_degrees
+#Input: List of degree values
+#Output: A graph with the indicated degree values if possible or 'DNE' if not.
+######################################
+sub GRgraph_degrees {
+ my (@input) = @_;
+ my ($i, $j, @adj, $ind, $val, $size, $graph, @degrees);
+
+ @degrees = reverse num_sort(@input);
+
+ $size = scalar @degrees;
+
+ if ($degrees[0] >= $size) {
+ $graph = 'DNE';
+ } else {
+ @adj = GRmatrix_graph(GRgraph_size_random($size, -1));
+ $val = 0;
+ $loc = 0;
+
+ while ($loc < $size && $val == 0) {
+ $val = $degrees[$loc] - VertDegree($loc, @adj);
+ $ind = $loc + 1;
+ while ($val > 0 && $ind < $size) {
+ if (VertDegree($ind, @adj) < $degrees[$ind]) {
+ $adj[$loc][$ind] = 1;
+ $adj[$ind][$loc] = 1;
+ $ind++;
+ $val--;
+ } else {
+ $ind++;
+ }
+ }
+ $loc++;
+ }
+
+ if ($val != 0) {
+ $graph = 'DNE';
+ } else {
+ $graph = GRgraph_matrix(@adj);
+ }
+ }
+ $graph;
+}
+
+######################################
+#Name: ChangeWeight
+#Input: Vertex numbers to change, weight to change the edge to and graph
+#Output: A graph with the edge between the indicated vertices changed to weight
+######################################
+sub ChangeWeight {
+ my ($i, $j, $weight, $graph) = @_;
+ my (@matrix);
+
+ @matrix = GRmatrix_graph($graph);
+ $matrix[$i][$j] = $weight;
+ $matrix[$j][$i] = $weight;
+
+ $graph = GRgraph_matrix(@matrix);
+}
+
+######################################
+#Name: GRhaseulercircuit_graph
+#Input: Graph
+#Output: A list containing a value and message. The value is 0 if it is not
+#an Euler circuit and 1 otherwise. The message relays why it is not.
+######################################
+sub GRhaseulercircuit_graph {
+ my ($graph) = @_;
+ my ($val, $comp, @answer, @degrees, $message, $isCircuit);
+
+ $isCircuit = 1;
+ $message = "Good";
+ $comp = GRncomponents_graph($graph);
+
+ if ($comp != 1) {
+ $isCircuit = 0;
+ $message = "Not connected";
+ }
+
+ @degrees = GRdegrees_graph($graph);
+
+ foreach $val (@degrees) {
+ if ($val % 2 != 0) {
+ $isCircuit = 0;
+ $message = "Incorrect degrees";
+ }
+ }
+
+ @answer = ($isCircuit, $message);
+}
+
+######################################
+#Name: GRhaseulertrail_graph
+#Input: Graph
+#Output: A list containing a value and message. The value is 0 if it is not
+#an Euler path and 1 otherwise. The message relays why it is not.
+######################################
+sub GRhaseulertrail_graph {
+ my ($graph) = @_;
+ my ($val, $odd, $comp, $even, $total, @answer, @degrees, $message, $isCircuit);
+
+ $isCircuit = 1;
+ $message = "Good";
+ $comp = GRncomponents_graph($graph);
+
+ if ($comp != 1) {
+ $isCircuit = 0;
+ $message = "Not connected";
+ }
+
+ @degrees = GRdegrees_graph($graph);
+
+ $odd = 0;
+ $even = 0;
+ $total = scalar @degrees;
+
+ foreach $val (@degrees) {
+ if ($val % 2 == 0) {
+ $even++;
+ } else {
+ $odd++;
+ }
+ }
+
+ if ($even != $total && $odd != 2) {
+ $isCircuit = 0;
+ $message = "Incorrect degrees";
+ }
+
+ @answer = ($isCircuit, $message);
+}
+
+######################################
+#Name: GReulercircuit_size
+#Input: Size of graph
+#Output: A graph that is an Euler Circuit
+######################################
+
+sub GReulercircuit_size {
+ my ($size) = @_;
+
+ my ($i, @deg, $pic, $comp, $diff, $graph, @lowdeg);
+
+ if ($size <= 4) {
+ die "Incorrect Size";
+ }
+
+ do {
+ @deg = ();
+ for ($i = 0; $i < $size; $i++) {
+ push @deg, random(2, $size - 1, 2);
+ }
+ $graph = GRgraph_degrees(@deg);
+ $comp = GRncomponents_graph($graph);
+ } while ($graph eq 'DNE' || $comp > 1);
+
+ $graph = GRshuffledgraph_graph($graph);
+
+}
+
+######################################
+#Name: GReulertrail_size
+#Input: Size of graph
+#Output: A graph that is an Euler Path but not an Euler Circuit
+######################################
+
+sub GReulertrail_size {
+ my ($size) = @_;
+
+ my ($i, @deg, $pic, $comp, $diff, $temp, $graph, @index, @lowdeg);
+
+=pod
+ for ($i = 0; $i < $size; $i++){
+ push @lowdeg, 2;
+ }
+
+ $comp = 2;
+ while ($graph eq 'DNE' || $comp > 1){
+ @deg = ();
+ for ($i = 0; $i < $size; $i++){
+ push @deg, random($lowdeg[$i],$size - 1,2);
+ }
+ $graph = GRgraph_degrees(@deg);
+ $comp = GRncomponents_graph($graph);
+
+ do {
+ $diff = random (0, $size - 1, 1);
+ } until ($lowdeg[$diff] < $size - 2);
+ $lowdeg[$diff] += 2;
+ }
+=cut
+
+ if ($size <= 4) {
+ die "Incorrect size";
+ }
+ do {
+ @deg = ();
+ for ($i = 0; $i < $size; $i++) {
+ push @deg, random(2, $size - 1, 2);
+ }
+ $graph = GRgraph_degrees(@deg);
+ $comp = GRncomponents_graph($graph);
+ } while ($graph eq 'DNE' || $comp > 1);
+
+ do {
+ @index = NchooseK($size, 2);
+ $temp = ChangeWeight(@index, 0, $graph);
+ } while ($temp eq $graph);
+
+ $graph = GRshuffledgraph_graph($temp);
+}
+
+######################################
+#Name: GRnoneuler_size
+#Input: Size of graph
+#Output: A graph that is not an Euler path
+######################################
+
+sub GRnoneuler_size {
+ my ($size) = @_;
+
+ my ($i, @ans, @deg, $pic, $diff, $graph, @lowdeg);
+
+ for ($i = 0; $i < $size; $i++) {
+ push @lowdeg, 2;
+ }
+
+ @ans = (1, '');
+
+=pod
+ while ($graph eq 'DNE' || $ans == 1){
+ @deg = ();
+ for ($i = 0; $i < $size; $i++){
+ push @deg, random($lowdeg[$i],$size - 1,1);
+ }
+ $graph = GRgraph_degrees(@deg);
+ if ($graph ne 'DNE'){
+ @ans = GRiseulertrail_graph($graph);
+ }
+
+ do {
+ $diff = random (0, $size - 1, 1);
+ } until ($lowdeg[$diff] < $size - 1);
+ $lowdeg[$diff]++;
+ }
+=cut
+
+ do {
+ @deg = ();
+ @ans = (1, '');
+ for ($i = 0; $i < $size; $i++) {
+ push @deg, random(2, $size - 1, 1);
+ }
+ $graph = GRgraph_degrees(@deg);
+ if ($graph ne 'DNE') {
+ @ans = GRhaseulertrail_graph($graph);
+ }
+ } while ($graph eq 'DNE' || $ans[0] == 1);
+
+ $graph = GRshuffledgraph_graph($graph);
+}
+
+######################################
+#Name: Matr_graph_Mult
+#Input:
+#Output:
+######################################
+sub Matr_graph_Mult {
+ my ($graph1, $graph2) = @_;
+
+ my ($i, $j, $k, @M1, @M2, $sum, $Gnew, @Mnew, $size);
+
+ @M1 = GRmatrix_graph($graph1);
+ @M2 = GRmatrix_graph($graph2);
+ $size = scalar @M1;
+
+ for ($i = 0; $i < $size; $i++) {
+ for ($j = 0; $j < $size; $j++) {
+ $sum = 0;
+ for ($k = 0; $k < $size; $k++) {
+ $sum += $M1[$i][$k] * $M2[$k][$j];
+ }
+ $Mnew[$i][$j] = $sum;
+ }
+ }
+
+ $Gnew = GRgraph_matrix(@Mnew);
+}
+
+######################################
+#Name: GRhascircuit_graph
+#Input: A graph
+#Output: 1 if the graph has a circuit, 0 otherwise
+######################################
+sub GRhascircuit_graph {
+ my ($graph) = @_;
+
+ my ($i, $j, $sum, $size, @matrix, $hasCircuit);
+
+ @matrix = GRmatrix_graph($graph);
+ $size = scalar @matrix;
+
+ for ($i = 0; $i < $size; $i++) {
+ $sum = 0;
+ for ($j = 0; $j < $size; $j++) {
+ $sum += $matrix[$i][$j];
+ }
+ if ($sum == 1) {
+ for ($j = 0; $j < $size; $j++) {
+ $matrix[$i][$j] = 0;
+ $matrix[$j][$i] = 0;
+ }
+ $i = -1;
+ }
+ }
+
+ for ($i = 0; $i < $size; $i++) {
+ for ($j = $i + 1; $j < $size; $j++) {
+ if ($matrix[$i][$j] != 0) {
+ $hasCircuit = 1;
+ last;
+ }
+ }
+ }
+
+ $hasCircuit;
+}
+
+######################################
+#Name: GRistree_graph
+#Input: A graph
+#Output: A list containing a 1 if it is a tree, 0 if not a tree and a
+# message about why it was not a tree.
+######################################
+sub GRistree_graph {
+ my ($graph) = @_;
+
+ my (@ans);
+
+ @ans = (1, 'Good');
+ if (GRhascircuit_graph($graph)) {
+ @ans = (0, 'Has a circuit.');
+ } elsif (GRncomponents_graph($graph) > 1) {
+ @ans = (0, 'Not connected.');
+ }
+ @ans;
+}
+
+######################################
+#Name: GRisforest_graph
+#Input: A graph
+#Output: A list containing a 1 if it is a forest, 0 if not a forest and a
+# message about why it was not a forest.
+######################################
+sub GRisforest_graph {
+ my ($graph) = @_;
+
+ my (@ans);
+
+ @ans = (1, 'Good');
+
+ if (GRhascircuit_graph($graph)) {
+ @ans = (0, 'Has a circuit');
+ }
+
+ @ans;
+}
+
+######################################
+#Name: GoodEdge
+#Input: A graph
+#Output: A 1 if the edge should be placed in a sorted edges based graph
+# 0 if not.
+######################################
+sub GoodEdge {
+ my ($graph) = @_;
+
+ my ($i, @adj, $ans, $sum, $val, $size);
+
+ @adj = GRmatrix_graph($graph);
+
+ $size = scalar @adj;
+ $ans = 1;
+ $sum = 0;
+
+ for ($i = 0; $i < $size; $i++) {
+ $val = VertDegree($i, @adj);
+ $sum += $val;
+ if ($val > 2) {
+ $ans = 0;
+ }
+ }
+
+ if ($sum < 2 * $size && GRhascircuit_graph($graph)) {
+ $ans = 0;
+ }
+
+ $ans;
+}
+
+######################################
+#Name: GRsortededges_graph
+#Input: A graph
+#Output: A list of the values of the edges included in the graph
+# in the order of their inclusion.
+######################################
+sub GRsortededges_graph {
+ my ($graph) = @_;
+ my ($i, $j, $val, $size, @path, @matrix, @weights, @newmatrix, $newgraph);
+
+ @matrix = GRmatrix_graph($graph);
+ $size = scalar @matrix;
+
+ for ($i = 0; $i < $size; $i++) {
+ $newmatrix[$i][$i] = 0;
+ for ($j = $i + 1; $j < $size; $j++) {
+ $newmatrix[$i][$j] = 0;
+ $newmatrix[$j][$i] = 0;
+ push @weights, $matrix[$i][$j];
+ }
+ }
+
+ @weights = num_sort(@weights);
+
+ @path = ();
+
+ do {
+ $val = shift @weights;
+ for ($i = 0; $i < $size; $i++) {
+ for ($j = $i + 1; $j < $size; $j++) {
+ if ($val == $matrix[$i][$j]) {
+ $newmatrix[$i][$j]++;
+ $newmatrix[$j][$i]++;
+ $newgraph = GRgraph_matrix(@newmatrix);
+ if (GoodEdge($newgraph)) {
+ push @path, $val;
+ } else {
+ $newmatrix[$i][$j]--;
+ $newmatrix[$j][$i]--;
+ }
+ }
+ }
+ }
+
+ } while (scalar @path < $size && scalar @weights > 0);
+
+ @path;
+}
+
+######################################
+#Name: GRiseulertrail_graph_vertices
+#Input: A graph and list of selected vertices
+#Output: A list containing a 1 if the indicated list of
+# vertices is an euler path in the graph or 0 if
+# it is not and a message indicating the reason.
+######################################
+sub GRiseulertrail_graph_vertices {
+ my ($graph, @verts) = @_;
+
+ my ($i, $j, @adj, @ans, $sum, $size);
+
+ @adj = GRmatrix_graph($graph);
+ $size = scalar @adj;
+ @ans = (1, 'Good');
+
+ $i = shift @verts;
+ do {
+ $j = shift @verts;
+ if ($adj[$i][$j] == 0) {
+ @ans = (0, 'Incorrect edge included');
+ }
+ $adj[$i][$j] = 0;
+ $adj[$j][$i] = 0;
+ $i = $j;
+ } while ($ans[0] == 1 && scalar @verts > 0);
+
+ $sum = 0;
+ for ($i = 0; $i < $size; $i++) {
+ for ($j = $i + 1; $j < $size; $j++) {
+ $sum += $adj[$i][$j];
+ }
+ }
+
+ if ($ans[0] != 0 && $sum != 0) {
+ @ans = (0, 'Not all edges used');
+ }
+
+ @ans;
+}
+
+######################################
+#Name: GRiseulercircuit_graph_vertices
+#Input: A graph and list of selected vertices
+#Output: A list containing a 1 if the indicated list of
+# vertices is an euler circuit in the graph or 0 if
+# it is not and a message indicating the reason.
+######################################
+sub GRiseulercircuit_graph_vertices {
+ my ($graph, @verts) = @_;
+
+ my ($i, $j, @adj, @ans, $sum, $size);
+
+ @adj = GRmatrix_graph($graph);
+ $size = scalar @verts;
+
+ @ans = GRiseulertrail_graph_vertices($graph, @verts);
+
+ if ($ans[0] != 0 && $verts[0] != $verts[ $size - 1 ]) {
+ @ans = (0, 'Not an Euler circuit');
+ }
+
+ @ans;
+}
+
+######################################
+#Name: GRforest_size
+#Input: Size of the desired graph to be a forest
+#Output: A graph that is a forest
+######################################
+sub GRforest_size {
+ my ($size) = @_;
+
+ my ($i, $j, $new, $graph);
+
+ $graph = GRtree_size($size);
+
+ do {
+ ($i, $j) = NchooseK($size, 2);
+ $new = ChangeWeight($i, $j, 0, $graph);
+ } while ($new eq $graph);
+
+ $new;
+}
+
+######################################
+#Name: GRtree_size
+#Input: size of desired tree
+#Output: A graph that is a tree
+######################################
+sub GRtree_size {
+ my ($size) = @_;
+
+ my ($i, $j, @adj, @used, @avail, $graph, @shuffle);
+
+ for ($i = 0; $i < $size; $i++) {
+ for ($j = 0; $j < $size; $j++) {
+ $adj[$i][$j] = 0;
+ }
+ }
+ $graph = GRgraph_matrix(@adj);
+
+ @avail = (0 .. $size - 1);
+ @shuffle = NchooseK($size, $size);
+ @avail = @avail[@shuffle];
+
+ $j = pop @avail;
+ push @used, $j;
+ do {
+ $j = pop @avail;
+ $i = random(0, scalar @used - 1, 1);
+ $graph = ChangeWeight($used[$i], $j, 1, $graph);
+ push @used, $j;
+ } while (scalar @avail > 0);
+
+ $graph;
+}
+
+######################################
+#Name: GRisbipartite_graph
+#Input: Graph object
+#Output: A 1 is the graph is bipartite, 0 otherwise
+######################################
+sub GRisbipartite_graph {
+ my ($graph) = @_;
+
+ my ($i, @adj, $ans, $col, $done, $size, $vert, @color, @verts, $colored, $orig_col, @Vertcolor);
+ @adj = GRmatrix_graph($graph);
+ $size = scalar @adj;
+
+ for ($i = 0; $i < $size; $i++) {
+ push @Vertcolor, 0;
+ }
+ $ans = 1;
+ @color = (1, 2);
+
+ do {
+ $i = 0;
+ while ($Vertcolor[$i] != 0) {
+ $i++;
+ }
+ $Vertcolor[$i] = $color[0];
+ push @verts, $i;
+
+ do {
+ $vert = shift @verts;
+ $orig_col = $Vertcolor[$vert];
+ for ($i = 0; $i < $size; $i++) {
+ if ($adj[$vert][$i] != 0) {
+ $col = $Vertcolor[$i];
+ if ($orig_col == $col) {
+ $ans = 0;
+ last;
+ } elsif ($col == 0) {
+ push @verts, $i;
+ $Vertcolor[$i] = $color[ 2 - $orig_col ];
+ }
+ }
+ }
+ } while (scalar @verts > 0 && $ans == 1);
+
+ $done = 1;
+ for ($i = 0; $i < $size; $i++) {
+ $done *= $Vertcolor[$i];
+ }
+ } while ($done == 0 && $ans == 1);
+
+ $ans;
+}
+
+######################################
+#Name: GRbipartite_size
+#Input: Size of the desired graph
+#Output: A graph that has is bipartite
+######################################
+sub GRbipartite_size {
+ my ($size) = @_;
+
+ my ($i, @P1, @P2, $s1, $s2, $max, $rnd1, $rnd2, $count, $graph);
+
+ $graph = GRgraph_size_random($size, -1);
+
+ $s1 = random(2, $size - 2, 1);
+ $s2 = $size - $s1;
+
+ if ($s1 > $s2) {
+ $max = $s1;
+ } else {
+ $max = $s2;
+ }
+
+ $count = random($max, 3 * $s1 * $s2 / 4, 1);
+
+ do {
+ $ind1 = random(0, $s1 - 1, 1);
+ $ind2 = $s1 + random(0, $s2 - 1, 1);
+ $graph = ChangeWeight($ind1, $ind2, 1, $graph);
+ @deg = GRdegrees_graph($graph);
+ $sum = 0;
+ for ($i = 0; $i < $size; $i++) {
+ $sum += $deg[$i];
+ }
+ } while ($sum < 2 * $count);
+
+ $graph = GRshuffledgraph_graph($graph);
+}
+
+######################################
+#Name: GRdijkstra_graph_vertex_vertex
+#Input: A graph, starting vertex and ending vertex for Dijkstra's algorithm
+#Output: Two lists, the first, a list of distances from the start vertex
+# to every other vertex. The second a list of the vertices preceding
+# the current vertex to achieve the shortest path.
+######################################
+sub GRdijkstra_graph_vertex_vertex {
+ my ($graph, $start, $end) = @_;
+
+ my ($i, @adj, $ind, $loc, $min, $new, @dist, $done, $left, @path, @prev, $size, @used, @avail, $weight);
+
+ @adj = GRmatrix_graph($graph);
+ $size = scalar @adj;
+ for ($i = 0; $i < $size; $i++) {
+ $dist[$i] = -1;
+ $prev[$i] = -1;
+ }
+ $dist[$start] = 0;
+
+ @avail = (0 .. $size - 1);
+ $left = scalar @avail;
+
+ while ($left > 0) { # && $new != $end){
+ $min = 1000000000;
+ for ($i = 0; $i < $left; $i++) {
+ $loc = $avail[$i];
+ if ($dist[$loc] >= 0 && $dist[$loc] < $min) {
+ $new = $loc;
+ $ind = $i;
+ $min = $dist[$new];
+ }
+ }
+ push @used, $new;
+ splice @avail, $ind, 1;
+ @used = num_sort(@used);
+
+ for ($i = 0; $i < $size; $i++) {
+ $weight = $adj[$new][$i];
+ if ($weight != 0) {
+ if ($dist[$i] > $min + $weight || $dist[$i] < 0) {
+ $dist[$i] = $min + $weight;
+ $prev[$i] = $new;
+ }
+ }
+ }
+ $left = scalar @avail;
+ }
+
+ $loc = $end;
+ while ($loc != $start) {
+ unshift @path, $loc;
+ $loc = $prev[$loc];
+ }
+ unshift @path, $start;
+
+ ($dist[$end], @path);
+}
+
+######################################
+#Name: GRgraphpic_dim_random_labels_weight_dweight
+#Input: Dimensions of the table in (row, column) format, a value to determine
+# the probability of an edge existing between vertices, labels for the vertices,
+# starting weight and weight difference for edge weights.
+#Output: A graph with weights and a picture of the graph in tabular format.
+######################################
+sub GRgraphpic_dim_random_labels_weight_dweight {
+ my ($row, $col, $prob, $labels, $weight, $dweight) = @_;
+
+ my (
+ $i, $j, $u, $v, $x, $y, $up, @adj, $lab,
+ $loc, $pic, $val, $base, $grid, $poss, $rght, $rtdn, $rtup,
+ $size, $new_x, $new_y, $shift, $down_y, $orig_x, $orig_y, $rght_x
+ );
+
+ @adj = GRmatrix_graph(GRgraph_size_random($row * $col, -1));
+
+ $base = 10;
+ $grid = 20;
+ $shift = $grid / 15;
+ $width = ($col - 1) * $grid;
+ $height = ($row - 1) * $grid;
+ $pic = init_graph(0, 0, ($col - .25) * $grid, ($row - .25) * $grid, 'pixels' => [ 7 * $width, 7 * $height ]);
+
+ for ($i = 0; $i < $row; $i++) {
+ for ($j = 0; $j < $col; $j++) {
+ $x = $base + $grid * $j;
+ $y = $base + $grid * $i;
+ $pic->stamps(closed_circle($x, $y, "blue"));
+ $lab = new Label(
+ $x - $shift,
+ $y + 2 * $shift,
+ GRlabel_vertex_labels($i + $row * $j, $labels),
+ 'blue', 'center', 'center'
+ );
+ $pic->lb($lab);
+ }
+ }
+
+ for ($i = 0; $i < $row; $i++) {
+ for ($j = 0; $j < $col; $j++) {
+ $loc = $j * $row + $i;
+ $rght = $loc + $row;
+ $up = $loc + 1;
+ $rtup = $loc + $row + 1;
+ $rtdn = $loc + $row - 1;
+
+ if ($j + 1 < $col && random(0, 1, .01) <= $prob) {
+ $count++;
+ $adj[$loc][$rght] = 1;
+ $adj[$rght][$loc] = 1;
+ }
+ if ($i + 1 < $row && random(0, 1, .01) <= $prob) {
+ $count++;
+ $adj[$loc][$up] = 1;
+ $adj[$up][$loc] = 1;
+ }
+ if ($i + 1 < $row && $j + 1 < $col && random(0, 1, .01) <= $prob) {
+ $count++;
+ $adj[$loc][$rtup] = 1;
+ $adj[$rtup][$loc] = 1;
+ }
+ if ($i - 1 >= 0 && $j + 1 < $col && random(0, 1, .01) <= $prob) {
+ $count++;
+ $adj[$loc][$rtdn] = 1;
+ $adj[$rtdn][$loc] = 1;
+ }
+ }
+ }
+
+ for ($i = 0; $i < $count; $i++) {
+ push @weights, $weight + $i * $dweight;
+ }
+ @weights = @weights[ NchooseK($count, $count) ];
+
+ $u = .3333;
+ $v = 1 - $u;
+
+ for ($i = 0; $i < $row * $col; $i++) {
+ for ($j = $i + 1; $j < $row * $col; $j++) {
+ if ($adj[$i][$j] != 0) {
+ if ($dweight == 0) {
+ $val = random(2, 15, 1);
+ } else {
+ $val = shift @weights;
+ }
+ $adj[$i][$j] = $val;
+ $adj[$j][$i] = $val;
+ $orig_x = $base + int($i / $row) * $grid;
+ $orig_y = $base + $i % $row * $grid;
+ $new_x = $base + int($j / $row) * $grid;
+ $new_y = $base + $j % $row * $grid;
+ $pic->moveTo($orig_x, $orig_y);
+ $pic->lineTo($new_x, $new_y, 9);
+ $lab = new Label(
+ $u * $orig_x + $v * $new_x,
+ $u * $orig_y + $v * $new_y + $shift,
+ $val, 'red', 'center', 'center'
+ );
+ $pic->lb($lab);
+ }
+ }
+ }
+
+ $graph = GRgraph_matrix(@adj);
+
+ $graph, $pic;
+}
+
+######################################
+#Name: GRhamiltonian_size
+#Input: Size of the desired graph (5 or greater)
+#Output: A graph that has is hamiltonian
+######################################
+sub GRhamiltonian_size {
+ my ($size) = @_;
+
+ my ($i, $j, @adj, $low, $comp, $high, $count, $edges, $graph);
+
+ @adj = GRmatrix_graph(GRgraph_size_random($size, -1));
+ $comp = $size * ($size - 1) / 2;
+ if ($size <= 5) {
+ $low = $size + 1;
+ $high = $comp - 1;
+ } else {
+ $low = int($comp / 3) + 1;
+ $high = int($comp / 2) + 1;
+ }
+ $edges = int random($low, $high, 1);
+
+ for ($i = 0; $i < $size; $i++) {
+ $j = ($i + 1) % $size;
+ $adj[$i][$j] = 1;
+ $adj[$j][$i] = 1;
+ }
+
+ $count = $size;
+ while ($count < $edges) {
+ ($i, $j) = NchooseK($size, 2);
+ if ($adj[$i][$j] == 0) {
+ $adj[$i][$j] = 1;
+ $adj[$j][$i] = 1;
+ $count++;
+ }
+ }
+
+ $graph = GRshuffledgraph_graph(GRgraph_matrix(@adj));
+}
+
+######################################
+#Name: GRnonhamiltonian_size_type
+#Input: Size of the desired graph and a type of non hamiltonian graph:
+# odd: Has two cycles joined by a single edge
+# even: Has a vertex of degree one
+# If the odd type, size must be at least 6
+#Output: A graph that has is not hamiltonian of the specified type
+######################################
+sub GRnonhamiltonian_size_type {
+ my ($size, $type) = @_;
+
+ my ($i, $j, $v1, $v2, @adj, $val, $count, $edges, $graph, $ncomp, $split);
+
+ $type = $type % 2;
+ @adj = GRmatrix_graph(GRgraph_size_random($size, -1));
+
+ if ($type == 0) {
+ $ncomp = ($size - 1) * ($size - 2) / 2;
+ $edges = random($size + 1, 2 * $ncomp / 3 + 1, 1);
+
+ for ($i = 0; $i < $size - 1; $i++) {
+ $j = $i + 1;
+ $adj[$i][$j] = 1;
+ $adj[$j][$i] = 1;
+ }
+ $adj[ $size - 2 ][0] = 1;
+ $adj[0][ $size - 2 ] = 1;
+
+ $count = $size;
+ do {
+ ($i, $j) = NchooseK($size - 1, 2);
+ if ($adj[$i][$j] == 0) {
+ $adj[$i][$j] = 1;
+ $adj[$j][$i] = 1;
+ $count++;
+ }
+ } while ($count < $edges);
+ } else { #type 1
+ if ($size < 6) {die}
+ $split = int($size / 2);
+
+ for ($i = 0; $i < $split; $i++) {
+ $j = ($i + 1) % $split;
+ $adj[$i][$j] = 1;
+ $adj[$j][$i] = 1;
+ }
+ for ($i = $split; $i < $size; $i++) {
+ $j = ($i + 1) % $size;
+ $adj[$i][$j] = 1;
+ $adj[$j][$i] = 1;
+ }
+ $adj[ $size - 1 ][$split] = 1;
+ $adj[$split][ $size - 1 ] = 1;
+
+ $edges = ($size + 1) + ($size - 6);
+ $count = $size + 1;
+
+ $maxtry = 4; # Nandor: I don't know if this is needed, just to be sure
+ while ($count < $edges && $maxtry > 0) { #
+ $maxtry--; #
+ $v1 = random(0, $split - 1, 1);
+ $v2 = ($v1 + 2) % $split;
+ if ($adj[$v1][$v2] == 0) {
+ $adj[$v1][$v2] = 1;
+ $adj[$v2][$v1] = 1;
+ $count++;
+ }
+ if ($adj[ $v1 + $split ][ $v2 + $split ] == 0) {
+ $adj[ $v1 + $split ][ $v2 + $split ] = 1;
+ $adj[ $v2 + $split ][ $v1 + $split ] = 1;
+ $count++;
+ }
+ }
+ }
+ $graph = &GRshuffledgraph_graph(GRgraph_matrix(@adj));
+ # $graph = &GRgraph_matrix(@adj);
+}
+
+######################################
+#Name: GRcycle_size
+#Input: Size for cycle
+#Output: A picture of a graph that is a cycle (with no labels).
+######################################
+sub GRcycle_size {
+ my ($size) = @_;
+
+ my ($i, $j, $gap, $pic);
+
+ $pic = init_graph(-1.5, -1.5, 1.5, 1.5, 'pixels' => [ 220, 220 ]);
+ $gap = 2.0 * 3.14 / $size;
+
+ for ($i = 0; $i < $size; $i++) {
+ $j = ($i + 1) % $size;
+ $pic->stamps(closed_circle(cos($i * $gap), sin($i * $gap), "blue"));
+ $pic->moveTo(cos($i * $gap), sin($i * $gap));
+ $pic->lineTo(cos($j * $gap), sin($j * $gap), 1);
+ }
+ $pic;
+}
+
+######################################
+#Name: GRcomplete_size
+#Input: Size for complete graph
+#Output: A picture of a complete graph (with no labels).
+######################################
+sub GRcomplete_size {
+ my ($size) = @_;
+
+ my ($i, $j, $gap, $pic);
+ $pic = init_graph(-1.5, -1.5, 1.5, 1.5, 'pixels' => [ 220, 220 ]);
+ $gap = 2.0 * 3.14 / $size;
+
+ for ($i = 0; $i < $size; $i++) {
+ $pic->stamps(closed_circle(cos($i * $gap), sin($i * $gap), "blue"));
+ for ($j = $i + 1; $j < $size; $j++) {
+ $pic->moveTo(cos($i * $gap), sin($i * $gap));
+ $pic->lineTo(cos($j * $gap), sin($j * $gap), 1);
+ }
+ }
+ $pic;
+}
+
+######################################
+#Name: GRwheel_size
+#Input: Size for wheel
+#Output: A picture of a graph that is a wheel (with no labels).
+######################################
+sub GRwheel_size {
+ my ($size) = @_;
+
+ my ($i, $j, $gap, $pic);
+
+ $pic = init_graph(-1.5, -1.5, 1.5, 1.5, 'pixels' => [ 220, 220 ]);
+ $gap = 2.0 * 3.14 / $size;
+
+ $pic->stamps(closed_circle(0, 0, "blue"));
+ for ($i = 0; $i < $size; $i++) {
+ $j = ($i + 1) % $size;
+ $pic->stamps(closed_circle(cos($i * $gap), sin($i * $gap), "blue"));
+ $pic->moveTo(cos($i * $gap), sin($i * $gap));
+ $pic->lineTo(cos($j * $gap), sin($j * $gap), 1);
+ $pic->moveTo(0, 0);
+ $pic->lineTo(cos($i * $gap), sin($i * $gap), 1);
+ }
+ $pic;
+}
+
+######################################
+#Name: GRcompletebipartite_size_size
+#Input: Size for upper and size for lower bipartite graph
+#Output: A picture of a bipartite graph with size and
+# size labels on top and bottem (and no labels).
+######################################
+sub GRcompletebipartite_size_size {
+ my ($m, $n) = @_;
+
+ my ($i, $j, $low, $pic, $diff, $high, @shft, $width, $x_max);
+
+ $low = 5;
+ $high = 20;
+ $width = 20;
+ @shft = (10, 10);
+
+ $diff = $m - $n;
+
+ if ($diff == 0) {
+ $x_max = $m * $width + $shft[0];
+ } elsif ($diff % 2 == 0 && $diff > 0) {
+ $x_max = $m * $width + $shft[0];
+ $shft[1] += $width * $diff / 2;
+ } elsif ($diff % 2 == 0) {
+ $x_max = $n * $width + $shft[0];
+ $shft[0] += -$width * $diff / 2;
+ } elsif ($diff > 0) {
+ $x_max = $m * $width + $shft[0];
+ $shft[1] += ($width / 2) * $diff;
+ } else {
+ $x_max = $n * $width + $shft[0];
+ $shft[0] += (-$width / 2) * $diff;
+ }
+
+ $pic = init_graph(0, 0, $x_max, 25, 'pixels' => [ 220, 220 ]);
+
+ for ($i = 0; $i < $m; $i++) {
+ $pic->stamps(closed_circle($i * $width + $shft[0], $high, "blue"));
+ }
+ for ($j = 0; $j < $n; $j++) {
+ $pic->stamps(closed_circle($j * $width + $shft[1], $low, "blue"));
+ }
+
+ for ($i = 0; $i < $m; $i++) {
+ for ($j = 0; $j < $n; $j++) {
+ $pic->moveTo($i * $width + $shft[0], $high);
+ $pic->lineTo($j * $width + $shft[1], $low, 1);
+ }
+ }
+ $pic;
+}
+
+######################################
+#Name: GRcube
+#Input: None
+#Output: A picture of a graph that is a flattened cube.
+######################################
+sub GRcube {
+ my ($i, $j, $S1, $min, $pic, @prev, @PREV, $Step, @large, @small);
+
+ $min = 5;
+ $S1 = 30;
+ $Step = 7.5;
+
+ $pic = init_graph(0, 0, $S1 + 2 * $min, $S1 + 2 * $min, 'pixels' => [ 220, 220 ]);
+
+ push @large, $min, $min + $S1, $min + $S1, $min;
+ push @small, $min + $Step, $min + $S1 - $Step, $min + $S1 - $Step, $min + $Step;
+
+ @PREV = ($large[0], $large[0]);
+ @prev = ($small[0], $small[0]);
+
+ for ($i = 0; $i < 4; $i++) {
+ $j = ($i + 1) % 4;
+ $pic->stamps(closed_circle($large[$i], $large[$j], "blue"));
+ $pic->stamps(closed_circle($small[$i], $small[$j], "blue"));
+ $pic->moveTo(@PREV);
+ $pic->lineTo($large[$i], $large[$j]);
+ $pic->lineTo($small[$i], $small[$j], 1);
+ $pic->lineTo(@prev);
+ @PREV = ($large[$i], $large[$j]);
+ @prev = ($small[$i], $small[$j]);
+ }
+ $pic;
+}
+
+######################################
+#Name: GRpetersen
+#Input: None
+#Output: A picture of the Petersen graph.
+######################################
+sub GRpetersen {
+ my ($i, $R1, $R2, $gap, $pic, $val, @prev, $val2, $shift);
+
+ $R1 = 1;
+ $R2 = 2;
+ $pic = init_graph(-$R2 - .5, -$R2 - .5, $R2 + .5, $R2 + .5, 'pixels' => [ 220, 220 ]);
+ $gap = 2.0 * 3.14 / 5;
+ $shift = $PI / 10;
+
+ @prev = ($R2 * cos($shift), $R2 * sin($shift));
+ for ($i = 0; $i < 5; $i++) {
+ $val = $i * $gap + $shift;
+ $val2 = ($i + 2) % 5 * $gap + $shift;
+ $pic->stamps(closed_circle($R1 * cos($val), $R1 * sin($val), "blue"));
+ $pic->stamps(closed_circle($R2 * cos($val), $R2 * sin($val), "blue"));
+ $pic->moveTo(@prev);
+ $pic->lineTo($R2 * cos($val), $R2 * sin($val));
+ $pic->lineTo($R1 * cos($val), $R1 * sin($val), 1);
+ $pic->lineTo($R1 * cos($val2), $R1 * sin($val2), 1);
+ @prev = ($R2 * cos($val), $R2 * sin($val));
+ }
+ $pic->moveTo(@prev);
+ $pic->lineTo($R2 * cos($shift), $R2 * sin($shift));
+
+ $pic;
+}
+
+######################################
+#Name: GRdodecahedron
+#Input: None
+#Output: A picture of a graph that is a flattened dodecahedron.
+######################################
+sub GRdodecahedron {
+ my ($i, $P1, $P2, $P3, $P4, $gap, $pic, $val, $val2, $shift, $space);
+
+ $P1 = 4;
+ $P2 = 2.75;
+ $P3 = 2;
+ $P4 = 1;
+
+ $pic = init_graph(-$P1 - .5, -$P1 - .5, $P1 + .5, $P1 + .5, 'pixels' => [ 220, 220 ]);
+ $gap = 2.0 * 3.14 / 5;
+ $space = $gap / 2;
+ $shift = $PI / 10;
+
+ @prev = ($P1 * cos($shift), $P1 * sin($shift));
+ for ($i = 0; $i < 5; $i++) {
+ $val = $i * $gap + $shift;
+ $val2 = $val + $space;
+ $pic->stamps(closed_circle($P1 * cos($val), $P1 * sin($val), "blue"));
+ $pic->stamps(closed_circle($P2 * cos($val), $P2 * sin($val), "blue"));
+ $pic->stamps(closed_circle($P3 * cos($val2), $P3 * sin($val2), "blue"));
+ $pic->stamps(closed_circle($P4 * cos($val2), $P4 * sin($val2), "blue"));
+ $pic->moveTo(@prev);
+ $pic->lineTo($P1 * cos($val), $P1 * sin($val));
+ $pic->lineTo($P2 * cos($val), $P2 * sin($val), 1);
+ $pic->lineTo($P3 * cos($val2), $P3 * sin($val2), 1);
+ $pic->lineTo($P4 * cos($val2), $P4 * sin($val2), 1);
+ $pic->lineTo($P4 * cos($val2 - $gap), $P4 * sin($val2 - $gap), 1);
+ $pic->moveTo($P2 * cos($val), $P2 * sin($val));
+ $pic->lineTo($P3 * cos($val2 - $gap), $P3 * sin($val2 - $gap), 1);
+ @prev = ($P1 * cos($val), $P1 * sin($val));
+ }
+ $pic->moveTo(@prev);
+ $pic->lineTo($P1 * cos($shift), $P1 * sin($shift));
+
+ $pic;
+}
+
+######################################
+#Name: GRvertices_labels_labels
+#Input: string of labels, string of all graph labels
+#Output: Returns a list of vertex numbers corresponding to the
+# labels in the first string.
+######################################
+sub GRvertices_labels_labels {
+ my ($labels, $orig_labels) = @_;
+
+ my ($i, $label, @vertices);
+
+ for ($i = 0; $i < length $labels; $i++) {
+ $label = substr $labels, $i, 1;
+ push @vertices, GRvertex_label_labels($label, $orig_labels);
+ }
+
+ @vertices;
+}
+
+1;
diff --git a/macros/math/PGnauScheduling.pl b/macros/math/PGnauScheduling.pl
new file mode 100644
index 0000000000..2a5ba74237
--- /dev/null
+++ b/macros/math/PGnauScheduling.pl
@@ -0,0 +1,1041 @@
+
+#####################################################################
+#
+# Name : ListProcEval (answer evaluator for the scheduling .pg files)
+#
+#####################################################################
+
+sub ListProcEval {
+ my (@input) = @_;
+ sub {
+ my $in = shift;
+ my $score = 0;
+ my $ans_hash;
+ my $plot_input;
+ my $bad_input = 0;
+ my $student_answer = "";
+ my @coordinates_weights = split ",", $input[0];
+
+ my $i = 0;
+ my $max_weight = 0;
+ while ($i < int(scalar @coordinates_weights)) {
+ if ($i % 3 == 2) { $max_weight += $coordinates_weights[$i]; }
+ $i++;
+ }
+
+ # form the correct string ...
+ my @temp = split ",", $input[ scalar @input - 1 ];
+ my $proc_label = shift @temp;
+ my $correct = join ",", @temp;
+
+ # check for 'bad' input ...
+ $in = uc $in;
+ $in =~ s/ //g;
+ my $temp_word = $in;
+
+ $temp_word =~ s/T//g;
+ for ($i = 0; $i < 10; $i++) { $temp_word =~ s/$i//g; }
+ $temp_word =~ s/,//g;
+
+ if ($temp_word ne "") {
+ $bad_input = 1;
+ } else {
+ $temp_word = $in;
+ $temp_word = uc $temp_word;
+ if ($temp_word =~ "TT" || $temp_word =~ "T,T") {
+ $bad_input = 1;
+ } else {
+ @temp = split ",", $temp_word;
+ for ($i = 0; $i < scalar @temp; $i++) {
+ if ($temp[$i] =~ "T") {
+ $temp[$i] =~ s/T//g;
+ if ($temp[$i] > (scalar @coordinates_weights) / 3) {
+ $bad_input = 1;
+ }
+ } else {
+ if ($temp[$i] > $max_weight) { $bad_input = 1; }
+ }
+ }
+ }
+ }
+ if ($bad_input) {
+ $score = 0;
+ $student_answer = 'The answer format is not correct';
+ } else {
+ $plot_input = join ",", $proc_label, $in;
+ if ($correct eq $in) {
+ $score = 1;
+ }
+ my $pic = DrawProcessorSchedule($input[0], $plot_input);
+ $pic->gifName($pic->gifName() . "-$plot_input");
+ $student_answer = Plot($pic);
+ }
+ $ans_hash = {
+ score => $score,
+ correct_ans => $correct,
+ student_ans => $student_answer,
+ preview_latex_string => "$in",
+ # ans_message => ($score) ? ' ' : ' '
+ };
+ $ans_hash;
+ }
+}
+
+#############################################################
+#
+# Name : MakeArrow
+#
+# Input : $i_1 = the 'tail' x_coordinate in pixels
+# $i_2 = the 'tail' y_coordinate in pixels
+# $j_1 = the 'head' x_coordinate in pixels
+# $j_2 = the 'head' y_coordinate in pixels
+# $size = the arrowhead size, in pixels
+# $pic = the graphics object to modify
+#
+# Output : $pic
+#
+#############################################################
+
+sub MakeArrow {
+ my ($i_1, $i_2, $j_1, $j_2, $size, $pic) = @_;
+ my ($diff1, $diff2, $angle);
+ $angle = atan2($j_2 - $i_2, $j_1 - $i_1);
+ $diff1 = $size * (cos($angle) + sin($angle));
+ $diff2 = $size * (cos($angle) - sin($angle));
+ $pic->moveTo($i_1, $i_2);
+ $pic->lineTo($j_1, $j_2, 1);
+ $pic->lineTo($j_1 - $diff1, $j_2 + $diff2, 1);
+ $pic->lineTo($j_1 - $diff2, $j_2 - $diff1, 1);
+ $pic->lineTo($j_1, $j_2, 1);
+}
+
+####################################################################################################
+#
+# Name : DrawSchedule
+#
+# Input : $coordinates = "row (0, ... , 10), column (0, ... , 10), weight, ... , ... "
+# The order determines the vertex index and label used.
+# $connections = "vertex index (tail), vertex index (head), ... , ... "
+# The vertex indices are taken according to the ordering in
+# $coordinates.
+#
+# Output : $pic = the graphics object.
+#
+####################################################################################################
+
+sub DrawSchedule {
+ my ($coordinates, $connections) = @_;
+ my (
+ @coordinates_weights, @connections, $y_coor_min, $x_coor_max,
+ $pic, $circleradius, $grid_space, $window_scale,
+ $xlower, $xupper, $ylower, $yupper,
+ @scaled_coordinates_weights, $offset_1, $offset_2, $this_angle,
+ $arrow_size, $i_1, $i_2, $j_1,
+ $j_2, $label_x, $label_y, $diff,
+ $vert_label, @circle, $i
+ );
+ @coordinates_weights = split ",", $coordinates;
+ @connections = split ",", $connections;
+ $i = 0;
+ while ($i < scalar @connections) { $connections[$i]--; $i++; }
+ # find the coordinate extremes.
+ $y_coor_min = 0;
+ $x_coor_max = 0;
+ $i = 0;
+ while ($i < scalar @coordinates_weights) {
+ if ($coordinates_weights[ $i + 1 ] > $x_coor_max) { $x_coor_max = $coordinates_weights[ $i + 1 ] }
+ if ($coordinates_weights[$i] > $y_coor_min) { $y_coor_min = $coordinates_weights[$i] }
+ $i += 3;
+ }
+ # check the coordinates, and reply with error if necessary
+ if (abs($y_coor_min) > 10 || abs($x_coor_max) > 10) {
+ die "The absolute value of any coordinate cannot exceed '10' ";
+ }
+ #scaling factors
+ $circleradius = 15;
+ $grid_space = 15;
+ $window_scale = 3;
+ # setup graph. The size is essentially fixed here.
+ $xlower = -$grid_space;
+ $xupper = $x_coor_max * $grid_space + $grid_space;
+ $ylower = -$y_coor_min * $grid_space - $grid_space;
+ $yupper = $grid_space;
+ $pic = init_graph($xlower, $ylower, $xupper, $yupper,
+ 'pixels' => [ $window_scale * ($xupper - $xlower), $window_scale * ($yupper - $ylower) ]);
+ # scale the coordinates
+ $i = 0;
+ while ($i < scalar @coordinates_weights) {
+ $scaled_coordinates_weights[ $i + 1 ] = $coordinates_weights[ $i + 1 ] * $grid_space;
+ $scaled_coordinates_weights[$i] = -$coordinates_weights[$i] * $grid_space;
+ $i += 3;
+ }
+ # add the arrows
+ $this_angle = 0;
+ $i = 0;
+ while ($i < scalar @connections) {
+ $offset_1 = $connections[$i] * 3;
+ $offset_2 = $connections[ $i + 1 ] * 3;
+ $arrow_size = 1.5;
+ $this_angle = atan2(
+ $scaled_coordinates_weights[$offset_2] - $scaled_coordinates_weights[$offset_1],
+ $scaled_coordinates_weights[ $offset_2 + 1 ] - $scaled_coordinates_weights[ $offset_1 + 1 ]
+ );
+ $i_1 = $scaled_coordinates_weights[ $offset_1 + 1 ] + $circleradius * cos($this_angle) / $window_scale;
+ $i_2 = $scaled_coordinates_weights[$offset_1] + $circleradius * sin($this_angle) / $window_scale;
+ $j_1 = $scaled_coordinates_weights[ $offset_2 + 1 ] - $circleradius * cos($this_angle) / $window_scale;
+ $j_2 = $scaled_coordinates_weights[$offset_2] - $circleradius * sin($this_angle) / $window_scale;
+ MakeArrow($i_1, $i_2, $j_1, $j_2, $arrow_size, $pic);
+ $i += 2;
+ }
+ # add the vertex circles
+ $i = 0;
+ while ($i < scalar @coordinates_weights) {
+ $label_x = $scaled_coordinates_weights[ $i + 1 ];
+ $label_y = $scaled_coordinates_weights[$i];
+ $diff = $circleradius / (1.5 * $window_scale);
+ $vert_label = $i / 3 + 1;
+ $circle[$i] = new Circle($label_x, $label_y, $circleradius, 'black', 'none');
+ $pic->stamps($circle[$i]);
+ $pic->lb(new Label($label_x, $label_y + $diff, "T$vert_label", 'black', 'center', 'center'));
+ $pic->lb(new Label($label_x, $label_y, $coordinates_weights[ $i + 2 ], 'black', 'center', 'center'));
+ $i += 3;
+ }
+ # the output
+ $pic;
+}
+
+######################################################################################################
+#
+# Name : ListProc (to determine the schedule from a priority list)
+#
+# Input : $coordinates = "row (0, ... , 10), column (0, ... , 10), weight, ... , ... "
+# The order determines the vertex index and label used.
+# $connections = "vertex index (tail), vertex index (head), ... , ... "
+# The vertex indices are taken according to the ordering in
+# $coordinates.
+# $order = The 'priority list' needed to realize the scheduling (p.79 textbook).
+# $machine_count = The number of available machines or processors.
+#
+# Output : @done_list = The 'raw' scheduling solution, indexed by machine:
+# "vertex index, time, vertex index, time, ...
+# The last indexed entry is the algorithm time.
+#
+#####################################################################################################
+
+sub ListProc {
+ my ($coordinates, $connections, $order, $machine_count) = @_;
+ my (
+ @coordinates_weights, @weights, @connections, @order, $task_count,
+ @not_ready, @ready, @machines, @dependence_count, @done_list,
+ $time, $done, $i, $j, $k,
+ $l, $max_weight, $onlist, $assigned, $done_count,
+ @machine_info, @these_dones
+ );
+
+ #intialize and setup
+ #####################################################
+ @coordinates_weights = split ",", $coordinates;
+ $i = 0;
+ $j = 0;
+ $max_weight = 0;
+ while ($i < int(scalar @coordinates_weights)) {
+ if ($i % 3 == 2) {
+ $weights[$j] = $coordinates_weights[$i];
+ $max_weights += $weights[$j];
+ $j++;
+ }
+ $i++;
+ }
+ @connections = split ",", $connections;
+ $i = 0;
+ while ($i < scalar @connections) { $connections[$i]--; $i++; }
+ $order =~ s/T//g;
+ @order = split ",", $order;
+ $i = 0;
+ while ($i < scalar @order) { $order[$i]--; $i++; }
+ $task_count = scalar @weights;
+ $i = 0;
+ while ($i < $machine_count) { $machines[$i] = "-1,-2"; $i++; }
+ $i = 0;
+
+ while ($i < $machine_count) {
+ $done_list[$i] = join "", ("P", $i + 1);
+ $i++;
+ }
+ $i = 0;
+ while ($i < $task_count) {
+ $dependence_count[$i] = 0;
+ $not_ready[$i] = $i;
+ $ready[$i] = -1;
+ $i++;
+ }
+ $j = 1;
+ while ($j < scalar @connections) {
+ $dependence_count[ $connections[$j] ]++;
+ $j += 2;
+ }
+ # done intializing
+ ##########################################
+ $time = 0;
+ $done = 0;
+ $done_count = 0;
+ while ($time < $max_weights && !$done) {
+ # update @ready
+ $i = 0;
+ while ($i < $task_count) {
+ # check if the current task is already on the 'ready' list
+ $onlist = 0;
+ $j = 0;
+ while ($j < $task_count && !$onlist) {
+ if ($ready[$j] == $i) { $onlist = 1; }
+ $j++;
+ }
+ # check readyness against current dependencies
+ if (!$onlist && $dependence_count[$i] == 0) {
+ unshift @ready, $i;
+ pop @ready;
+ }
+ $i++;
+ }
+ $j = 0;
+ while ($j < $task_count) {
+ # check if the highest priority task is on the 'ready' list
+ $k = 0;
+ while ($k < $task_count) {
+ if ($order[$j] == $ready[$k]) {
+ # check for an empty machine
+ $l = 0;
+ $assigned = 0;
+ while ($l < $machine_count && !$assigned) {
+ @machine_info = split ",", $machines[$l];
+ if ($machine_info[0] == -1) {
+ # add the ready task to this machine
+ $assigned = 1;
+ $machines[$l] = join ",", ($ready[$k], $weights[ $ready[$k] ]);
+ # change the dependence count to '-1' to indicate processing.
+ $dependence_count[ $ready[$k] ] = -1;
+ #remove the ready task from the list
+ splice @ready, $k, 1;
+ #free a vacant spot
+ push @ready, -1;
+ }
+ $l++;
+ }
+ }
+ $k++;
+ }
+ $j++;
+ }
+ $time++;
+ # update the remaining processing times
+ $i = 0;
+ while ($i < $machine_count) {
+ @machine_info = split ",", $machines[$i];
+ if ($machine_info[0] != -1) {
+ $machine_info[1]--;
+ # update
+ $machines[$i] = join ",", ($machine_info[0], $machine_info[1]);
+ }
+ $i++;
+ }
+ # check for 'freed' machines
+ $i = 0;
+ while ($i < $machine_count) {
+ @machine_info = split ",", $machines[$i];
+ if ($machine_info[0] != -1) {
+ if ($machine_info[1] == 0) {
+ # found a 'done' task
+ $done_list[$i] = join ",", $done_list[$i], ($machine_info[0] + 1, $time);
+ push @these_dones, $machine_info[0];
+ $machines[$i] = "-1,-2";
+ $done_count++;
+ }
+ }
+ $i++;
+ }
+ # check for stop condition, otherwise, update dependencies if necessary.
+ if ($done_count == $task_count) {
+ $done = 1;
+ } else {
+ while (scalar @these_dones > 0) {
+ $j = pop @these_dones;
+ $k = 0;
+ while ($k < scalar @connections) {
+ if ($connections[$k] == $j) { $dependence_count[ $connections[ $k + 1 ] ]--; }
+ $k += 2;
+ }
+ }
+ }
+ }
+ # build the 'slack time' answer format
+ #
+ # add the "T's" to the labels
+ $i = 0;
+ while ($i < scalar @done_list) {
+ $j = 1;
+ @this_list = split ",", $done_list[$i];
+ while ($j < scalar @this_list) {
+ $this_list[$j] = join "", ("T", $this_list[$j]);
+ $j += 2;
+ }
+ $done_list[$i] = join ",", @this_list;
+ $i++;
+ }
+ # cycle through each machine, and correct the format
+ $i = 0;
+ while ($i < scalar @done_list) {
+ @this_list = split ",", $done_list[$i];
+ # add any slack time before the first task
+ $offset = 0;
+ if (scalar @this_list >= 3) {
+ $this_task = $this_list[1];
+ $this_done = $this_list[2];
+ $this_task =~ s/T//g;
+ if ($this_done - $weights[ $this_task - 1 ] > 0) {
+ $temp = shift @this_list;
+ unshift @this_list, ($this_done - $weights[ $this_task - 1 ]);
+ unshift @this_list, $temp;
+ $offset++;
+ }
+ }
+ #check this machine schedule for slack time. Enter '-1' if no slack time.
+ $j = 1 + $offset;
+ while ($j < (scalar @this_list - 3)) {
+ # check for slack time
+ $next_task = $this_list[ $j + 2 ];
+ $next_task =~ s/T//g;
+ $this_done = $this_list[ $j + 1 ];
+ $next_done = $this_list[ $j + 3 ];
+ $next_weight = $weights[ $next_task - 1 ];
+ if ($next_done - $next_weight == $this_done) {
+ #no slack time
+ $this_list[ $j + 1 ] = -1;
+ } else {
+ #enter slack time
+ $this_list[ $j + 1 ] = ($next_done - $next_weight) - $this_done;
+ }
+ $j += 2;
+ }
+ # check for slack time on the last task
+ if (scalar @this_list == 1) { push @this_list, $time; }
+ if (scalar @this_list > 2) {
+ if ($this_list[ (scalar @this_list) - 1 ] == $time) {
+ $this_list[ (scalar @this_list) - 1 ] = -1;
+ } else {
+ $this_list[ (scalar @this_list) - 1 ] = $time - $this_list[ (scalar @this_list) - 1 ];
+ }
+ }
+ #update
+ $j = 2 + $offset;
+ while ($j < scalar @this_list) {
+ if ($this_list[$j] == -1) {
+ splice @this_list, $j, 1;
+ $j++;
+ } else {
+ $j += 2;
+ }
+ }
+ # final update
+ $done_list[$i] = join ",", @this_list;
+ $i++;
+ }
+ push @done_list, $time;
+ @done_list;
+}
+
+###########################################################
+# To generate a randomly ordered list of distinct integers
+###########################################################
+
+sub RandomIntList {
+ my ($max) = @_;
+ my ($i, $list, @integer_list, @list, $index);
+ while (scalar @integer_list < $max) {
+ push @integer_list, (scalar @integer_list + 1);
+ }
+ $i = 0;
+ while ($i < $max) {
+ $index = random(0, $max - $i - 1, 1);
+ push @list, $integer_list[$index];
+ splice @integer_list, $index, 1;
+ $i++;
+ }
+ $list = join ",", @list;
+ $list;
+}
+
+############################################################################################################################
+#
+# Name : FindPathsSchedule ( Works only for appropriate schedule type graphs )
+#
+# Input : $start_vertex = An index indicating the 'start vertex' to consider. Indices here START WITH ZERO.
+# $connections = The connectivity vector to use to determine the paths. Indices here START WITH ZERO.
+#
+# Output : @path_list = stores all possible paths from the 'start' vertex.
+#
+############################################################################################################################
+
+sub FindPathsSchedule {
+ my ($start_vertex, $connections) = @_;
+ my (@path_list, $i, $j, $k, $path_update, $list_count, $this_vertex, $previous_vertex, @these_connections,
+ $first_pass, @this_path, $count, @update, @connections);
+
+ # initialize
+ @connections = split ",", $connections;
+ @path_list = ("$start_vertex");
+ $count = 0;
+ $path_update = 1;
+ while ($path_update) {
+ $list_count = scalar @path_list;
+ for ($j = 0; $j < $list_count; $j++) {
+ @this_path = split ",", $path_list[$j];
+ $this_vertex = $this_path[ scalar @this_path - 1 ];
+ $previous_vertex = $this_path[ scalar @this_path - 2 ];
+ # find all possible connections for '$this_vertex'
+ $k = 1;
+ @these_connections = ();
+ while ($k < scalar @connections) {
+ if ($connections[ $k - 1 ] == $this_vertex) {
+ push @these_connections, $connections[$k];
+ }
+ $k += 2;
+ }
+ $first_pass = 1;
+ $update[$j] = 0;
+ for ($i = 0; $i < scalar @these_connections; $i++) {
+ $update[$j] = 1;
+ if ($first_pass) {
+ $first_pass = 0;
+ $path_list[$j] = join ",", $path_list[$j], $these_connections[$i];
+ } else {
+ push @path_list, (join ",", @this_path);
+ $path_list[ scalar @path_list - 1 ] = join ",", $path_list[ scalar @path_list - 1 ],
+ $these_connections[$i];
+ }
+ }
+ }
+ # check if any paths were updated
+ while (scalar @update > 0) {
+ $path_update = 0;
+ $temp = pop @update;
+ if ($temp == 1) { $path_update = 1; }
+ }
+ # to avoid infinite loops only.
+ $count++;
+ if ($count > 1000) {
+ die "Infinite loop, perhaps ... ";
+ $path_update = 0;
+ }
+ }
+ @path_list;
+}
+
+###########################################################################################################
+#
+# Name : CritList ( Produces a priority list according to the critical path scheduling algorithm )
+#
+# Input : $coordinates = "row (0, ... , 10), column (0, ... , 10), weight, ... , ... "
+# The order determines the vertex index and label used.
+# $connections = "vertex index (tail), vertex index (head), ... , ... "
+# The vertex indices are taken according to the ordering in
+# $coordinates.
+#
+# Output : $priority_list = The priority list to use, according to the critical path
+# algorithm. (The tasks are numerically labeled beginning with
+# 1 according to the order of $coordinates.)
+#
+#####################################################################################################
+
+sub CritList {
+ my ($coordinates, $connections) = @_;
+ my (
+ $priority_list, @coordinates_weights, @these_coordinates_weights, @weights,
+ $task_count, $i, $j, $k,
+ $max_weights, $scheduled_count, @left_vertices, $this_vertex,
+ @these_paths, @this_path, $path_weight, $max_path_weight,
+ $max_path_index, $done, $prioritized, @connection_array,
+ $current_vertex, @temp, @temp_list, $max_vertex,
+ $independent, $crit_path, $these_connections, $these_coordinates,
+ $temp_word
+ );
+
+ #intialize and setup
+ #####################################################
+ @coordinates_weights = split ",", $coordinates;
+ @these_coordinates_weights = @coordinates_weights;
+ $i = 0;
+ $j = 0;
+ $max_weight = 0;
+ while ($i < int(scalar @coordinates_weights)) {
+ if ($i % 3 == 2) {
+ $weights[$j] = $coordinates_weights[$i];
+ $max_weights += $weights[$j];
+ $j++;
+ }
+ $i++;
+ }
+ $task_count = scalar @weights;
+ @connection_array = split ",", $connections;
+ $priority_list = "";
+ # end intialize and setup
+ #####################################################
+ $scheduled_count = 0;
+ while ($scheduled_count < $task_count) {
+ # update priority list
+ $these_connections = join ",", @connection_array;
+ $these_coordinates = join ",", @these_coordinates_weights;
+ $crit_path = CritPath($these_coordinates, $these_connections);
+ # remove the path weight, which is of no current use
+ @temp = split ";", $crit_path;
+ # convert labels to numbers
+ $temp[0] =~ s/T//g;
+ @this_path = split ",", $temp[0];
+ $this_task = shift(@this_path);
+ @temp_list = split ",", $priority_list;
+ push @temp_list, $this_task;
+ $priority_list = join ",", @temp_list;
+ # remove the connection corresponding to the critical path
+ $j = 0;
+ while ($j < scalar @connection_array) {
+ if ($connection_array[$j] == $this_task) {
+ splice @connection_array, $j, 2;
+ } else {
+ $j += 2;
+ }
+ }
+ # update coordinates to indicate which tasks are already prioritized ...
+ splice @these_coordinates_weights, ($this_task - 1) * 3, 3, (-1, -1, -1);
+ ### to temporarily force a stop
+ $scheduled_count++;
+ if ($scheduled_count > 1000) {
+ die "possible infinite loop";
+ }
+ }
+ # add the "T" notation ...
+ $priority_list = join "", "T", $priority_list;
+ $priority_list =~ s/,/,T/g;
+ $priority_list;
+}
+
+##########################################################################################################
+#
+# Name : CritPath ( Finds the critical path in a schedule type graph. 'Ties' are broken by comparing
+# the task labels of the first task at which two paths disagree. The smaller label
+# is taken as the critical path. )
+#
+# Input : $coordinates = "row (0, ... , 10), column (0, ... , 10), weight, ... , ... "
+# The order determines the vertex index and label used.
+# If -1,-1,-1 appears for any vertex, that task is not considered
+# as part of the graph.
+# $connections = "vertex index (tail), vertex index (head), ... , ... "
+# The vertex indices are taken according to the ordering in
+# $coordinates.
+#
+# Output : $crit_path = "task_label1, task_label2, ... , $task_labeln; total_weight
+#
+###########################################################################################################
+
+sub CritPath {
+ my ($coordinates, $connections) = @_;
+ my (
+ @coordinates_weights, @weights, $task_count, $i, $j,
+ $k, $scheduled_count, @left_vertices, $this_vertex, @these_paths,
+ @this_path, $path_weight, $max_path_weight, $max_path_index, $done,
+ $prioritized, @connection_array, $current_vertex, @temp, $max_vertex,
+ $independent
+ );
+
+ #intialize and setup
+ #####################################################
+ @coordinates_weights = split ",", $coordinates;
+ $i = 0;
+ $j = 0;
+ while ($i < int(scalar @coordinates_weights)) {
+ if ($i % 3 == 2) {
+ $weights[$j] = $coordinates_weights[$i];
+ $j++;
+ }
+ $i++;
+ }
+ $task_count = scalar @weights;
+ @connection_array = split ",", $connections;
+ $i = 0;
+ while ($i < scalar @connection_array) { $connection_array[$i]--; $i++; }
+ # end intialize and setup
+ #####################################################
+
+ # find all possible starting vertices ...
+ @left_vertices = ();
+ $j = 0;
+ while ($j < scalar @connection_array) {
+ $this_vertex = $connection_array[$j];
+ push @left_vertices, $this_vertex;
+ # check if the vertex was already found
+ for ($k = 0; $k < scalar @left_vertices - 1; $k++) {
+ if ($this_vertex == $left_vertices[$k]) {
+ pop @left_vertices;
+ $k = scalar @left_vertices - 1;
+ }
+ }
+ # check if we have a right vertex
+ $k = 1;
+ while ($k < scalar @connection_array) {
+ if ($connection_array[$k] == $this_vertex) {
+ pop @left_vertices;
+ $k = scalar @connection_array;
+ } else {
+ $k += 2;
+ }
+ }
+ $j += 2;
+ }
+ # check for 'independent' tasks
+ for ($j = 0; $j < $task_count; $j++) {
+ $independent = 1;
+ # check if the vertex is connected
+ for ($k = 0; $k < scalar @connection_array; $k++) {
+ if ($connection_array[$k] == $j) {
+ $independent = 0;
+ }
+ }
+ # add this independent task
+ if ($independent && $coordinates_weights[ $j * 3 ] != -1) { push @left_vertices, $j; }
+ }
+ # find ALL paths ...
+ $j = 0;
+ @these_paths = ();
+ while ($j < scalar @left_vertices) {
+ $this_vertex = $left_vertices[$j];
+ push @these_paths, FindPathsSchedule($this_vertex, (join ",", @connection_array));
+ $j++;
+ }
+ # determine the CRITICAL path ...
+ $j = 0;
+ $max_path_weight = 0;
+ $max_path_index = 0;
+ while ($j < scalar @these_paths) {
+ @this_path = split ",", $these_paths[$j];
+ $current_vertex = $this_path[0];
+ @temp = split ",", $these_paths[$max_path_index];
+ $max_vertex = shift @temp;
+ $path_weight = 0;
+ while (scalar @this_path > 0) {
+ $this_vertex = pop @this_path;
+ $path_weight = $path_weight + $weights[$this_vertex];
+ }
+ if ($path_weight > $max_path_weight) {
+ $max_path_weight = $path_weight;
+ $max_path_index = $j;
+ } else {
+ if ($path_weight == $max_path_weight) {
+ if ($current_vertex < $max_vertex) {
+ $max_path_weight = $path_weight;
+ $max_path_index = $j;
+ }
+ if ($current_vertex == $max_vertex) {
+ # the paths need to be compared according to vertex labels ...
+ @temp = split ",", $these_paths[$max_path_index];
+ $k = 0;
+ while ($k < scalar @temp && $k < scalar @this_path) {
+ if ($this_path[$k] < $temp[$k]) {
+ $max_path_weight = $path_weight;
+ $max_path_index = $j;
+ $k = scalar @temp - 1;
+ }
+ if ($this_path[$k] > $temp[$k]) {
+ $k = scalar @temp - 1;
+ }
+ $k++;
+ }
+ }
+ }
+ }
+ $j++;
+ }
+ # update and output
+ $crit_path = $these_paths[$max_path_index];
+ @temp = split ",", $crit_path;
+ $j = 0;
+ while ($j < scalar @temp) {
+ $temp[$j]++;
+ $j++;
+ }
+ $crit_path = join ",", @temp;
+ $crit_path = join "", "T", $crit_path;
+ $crit_path =~ s/,/,T/g;
+ $crit_path = join ";", $crit_path, $max_path_weight;
+ $crit_path;
+}
+
+#######################################################################################################
+#
+# Name : PickSchedule
+#
+# Input : $random_flag = if zero, empty or out-of-range, a random choice is made. If positive
+# and in-range, this indexes the graph to use. If "easy", then an 'easy'
+# graph is chosen.
+#
+# Output : $graph_info = $coordinates ; $connections where:
+#
+# $coordinates = "row (0, ... , 10), column (0, ... , 10), weight, ... , ... "
+# The order determines the vertex index and label used.
+# $connections = "vertex index (tail), vertex index (head), ... , ... "
+# The vertex indices are taken according to the ordering in
+# $coordinates.
+#
+########################################################################################################
+
+sub PickSchedule {
+
+ my ($random_flag) = @_;
+ my ($temp, $coordinates, $connections, $this_graph, $graph_info, @vertex_data, @connect_data, @vertices_weights,
+ $i);
+
+ # screen the input variable ...
+ if ($random_flag) {
+ $temp = $random_flag;
+ if ($temp =~ "easy" || $temp =~ "Easy") {
+ $random_flag =~ "easy";
+ } else {
+ for ($i = 0; $i < 10; $i++) { $temp =~ s/$i//g; }
+ if ($temp ne "") { $random_flag = 0; }
+ }
+ }
+
+ # load some possible graphs ...
+ $vertex_data[0] = "2,0,9,1,1,9,3,1,9,0,2,9,2,2,9,4,2,9,1,3,9,3,3,9";
+ $connect_data[0] = "1,2,1,3,1,5,2,4,2,5,3,6,4,7,5,7,5,8,6,8,1,7";
+
+ $vertex_data[1] = "1,0,14,0,3,14,1,6,36,2,3,14,4,0,4,3,1,6,3,5,16,5,1,4,5,3,4,5,5,16";
+ $connect_data[1] = "1,2,1,4,5,6,5,8,6,4,8,9,9,10,4,7,2,3,4,3";
+
+ $vertex_data[2] = "1,0,8,1,1,6,2,2,9,2,3,4,0,2,5,0,3,3,0,0,5,0,1,7";
+ $connect_data[2] = "1,2,2,3,3,4,5,6,2,5";
+
+ $vertex_data[3] = "0,0,2,1,0,1,2,0,1,3,0,1,1,3,3,2,4,3,3,3,3,4,2,3,0,2,8";
+ $connect_data[3] = "1,9,4,5,4,6,4,7,4,8";
+
+ $vertex_data[4] = "2,0,13,5,0,18,0,2,12,4,2,9,2,3,8,5,4,20";
+ $connect_data[4] = "1,3,1,4,2,4,3,5,4,5,2,6";
+
+ $vertex_data[5] = "1,0,1,1,1,1,0,2,1,2,2,1,1,3,1,2,4,1,2,5,1";
+ $connect_data[5] = "1,2,2,3,2,4,4,5,3,5,4,6,6,7";
+
+ $vertex_data[6] = "1,0,1,0,1,1,2,1,1,1,2,1,0,3,1,2,3,1,2,4,1";
+ $connect_data[6] = "1,2,1,3,2,4,3,4,2,5,3,6,6,7";
+
+ $vertex_data[7] = "0,0,10,1,0,3,2,0,8,0,3,15,1,3,4,2,3,20";
+ $connect_data[7] = "2,4,2,5,3,5";
+
+ $vertex_data[8] = "0,0,8,2,0,9,4,0,3,6,0,10,1,2,12,4,2,5,2,4,6,6,4,10";
+ $connect_data[8] = "1,5,2,5,3,6,4,8,5,7,5,8";
+
+ $vertex_data[9] = "0,0,8,0,2,6,0,4,3,2,0,5,2,2,2,2,4,9,4,0,7";
+ $connect_data[9] = "1,2,4,2,4,5,2,3,5,3,5,6";
+
+ $vertex_data[10] = "0,0,7,0,2,6,0,4,7,2,0,2,2,2,13,2,4,6,4,0,1,4,2,5,4,4,8";
+ $connect_data[10] = "1,2,4,5,7,5,7,8,2,3,2,6,5,6,8,9";
+
+ $vertex_data[11] = "1,0,8,0,1,5,2,1,6,1,2,5,0,3,7";
+ $connect_data[11] = "1,2,1,3,2,4,2,5,3,4";
+
+ $vertex_data[12] = "0,0,12,2,0,9,4,0,7,0,2,13,2,2,15,0,4,20,4,3,10";
+ $connect_data[12] = "1,4,2,5,3,5,4,6,5,6,3,7,7,6";
+
+ $vertex_data[13] = "1,0,8,1,1,6,2,2,9,2,4,4,0,2,5,0,4,3,0,0,5,0,1,7";
+ $connect_data[13] = "1,2,2,5,2,3,5,6,3,4";
+
+ $vertex_data[14] = "0,0,3,1,0,2,2,0,2,3,0,2,1,3,4,2,4,4,3,3,4,4,2,4,0,2,9";
+ $connect_data[14] = "1,9,4,5,4,6,4,7,4,8";
+
+ $vertex_data[15] = "0,0,1,1,0,1,2,0,1,3,0,1,4,0,10,5,0,10,6,0,10,0,3,3,1,3,3,2,3,3,3,3,3,1.5,4.5,10";
+ $connect_data[15] = "1,8,1,9,1,10,1,11,8,12,9,12,10,12,11,12";
+
+ $vertex_data[16] = "1,0,14,0,3,14,1,6,36,2,3,14,4,0,4,3,1,6,3,5,16,5,1,4,5,3,4,5,5,16,4,6,20,0,7,30,2,7,10";
+ $connect_data[16] = "1,2,1,4,5,6,5,8,6,4,8,9,9,10,4,7,2,3,4,3,10,11,7,11,3,12,3,13,7,13";
+
+ $vertex_data[17] = "0,0,4,2,0,5,4,0,3,1.5,4,3,1,2,8,3,2,7";
+ $connect_data[17] = "1,5,2,5,3,6,5,4,6,4";
+
+ $vertex_data[18] = "0,0,3,0,3,5,0,6,8,2,0,4,2,3,6,2,6,2";
+ $connect_data[18] = "1,2,1,5,4,5,2,3,5,3,5,6";
+
+ $vertex_data[19] = "2,0,5,4,0,9,0,0,3,2,3,5,2,6,3,4,6,2,4,3,5";
+ $connect_data[19] = "1,4,2,4,2,7,4,5,4,6";
+
+ $vertex_data[20] = "0,0,8,2,1,10,0,2,12,2,3,15,0,4,5,2,5,9,2,7,8,0,6,13";
+ $connect_data[20] = "1,3,2,3,2,4,3,5,4,6,5,6,5,8,6,7";
+
+ $vertex_data[21] = "0,0,7,0,3,9,0,6,5,2,0,8,2,3,6,2,6,7";
+ $connect_data[21] = "1,2,4,5,2,3,5,3,5,6,2,6";
+
+ # ************ ADD ANY NEW GRAPHS HERE ********************
+
+ # pick the graph
+ if ($random_flag) {
+ if ($random_flag =~ "easy") {
+ $this_graph = list_random(3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20, 21, 22) - 1;
+ # randomize the weights
+ @vertices_weights = split ",", $vertex_data[$this_graph];
+ $i = 2;
+ while ($i < scalar @vertices_weights) {
+ $vertices_weights[$i] = list_random(1, 2, 5);
+ $i += 3;
+ }
+ $vertex_data[$this_graph] = join ",", @vertices_weights;
+ } else {
+ if ($random_flag >= 1 && $random_flag <= scalar @vertex_data) {
+ $this_graph = $random_flag - 1;
+ } else {
+ $this_graph = random(0, (scalar @vertex_data) - 1, 1);
+ }
+ }
+ } else {
+ $this_graph = random(0, (scalar @vertex_data) - 1, 1);
+ @vertices_weights = split ",", $vertex_data[$this_graph];
+ $i = 2;
+ while ($i < scalar @vertices_weights) {
+ $vertices_weights[$i] = random(1, 9, 1);
+ $i += 3;
+ }
+ $vertex_data[$this_graph] = join ",", @vertices_weights;
+ }
+
+ # randomize the connectivity (?)
+
+ # ready to output
+ $coordinates = $vertex_data[$this_graph];
+ $connections = $connect_data[$this_graph];
+ $graph_info = join ";", $coordinates, $connections;
+ $graph_info;
+}
+
+#######################################################################################################
+#
+# Name : DrawProcessorSchedule
+#
+# Input : $coordinates = "row (0, ... , 10), column (0, ... , 10), weight, ... , ... "
+# The order determines the vertex index and label used. The
+# weights are needed to draw the schedules.
+# $done_list = The scheduling solution, for the current processor.
+#
+# Output : $schedule_graph = graphics object.
+#
+########################################################################################################
+
+sub DrawProcessorSchedule {
+
+ my ($coordinates, $done_list) = @_;
+ my (
+ $i, $j, $max_weights, $window_size, $xlower,
+ $xupper, $ylower, $yupper, $yuppermargin, $pic,
+ @coordinates_weights, @weights, @done_array, $total_time, $this_task_index,
+ @break_lines, @scaled_break_lines, @shade_region, $label, $lower_region,
+ $processor_label, $margin
+ );
+
+ @coordinates_weights = split ",", $coordinates;
+ $i = 0;
+ $j = 0;
+ $max_weight = 0;
+ while ($i < int(scalar @coordinates_weights)) {
+ if ($i % 3 == 2) {
+ $weights[$j] = $coordinates_weights[$i];
+ $max_weights += $weights[$j];
+ $j++;
+ }
+ $i++;
+ }
+ @done_array = split ",", $done_list;
+ $processor_label = shift @done_array;
+
+ # setup graph. The size is essentially fixed here.
+ $window_size = 4;
+ $yuppermargin = 1;
+ $margin = 5;
+ $xlower = -$margin;
+ $xupper = 100 + $margin;
+ $ylower = -$margin;
+ $yupper = 8 + $yuppermargin;
+ $pic = init_graph($xlower, $ylower, $xupper, $yupper,
+ 'pixels' => [ $window_size * ($xupper - $xlower), $window_size * ($yupper - $ylower) ]);
+ $pic->moveTo($xlower + $margin, $ylower + $margin);
+ $pic->lineTo($xupper - $margin, $ylower + $margin, 1);
+ $pic->lineTo($xupper - $margin, $yupper - $yuppermargin, 1);
+ $pic->lineTo($xlower + $margin, $yupper - $yuppermargin, 1);
+ $pic->lineTo($xlower + $margin, $ylower + $margin, 1);
+
+ # determine the total processing time, build the 'break_lines' and 'shade region' array
+ $i = 0;
+ $total_time = 0;
+ while ($i < scalar @done_array) {
+ if ($done_array[$i] =~ "T") {
+ $this_task_index = $done_array[$i];
+ $this_task_index =~ s/T//g;
+ $this_task_index--;
+ $total_time += $weights[$this_task_index];
+ $break_lines[$i] = $total_time;
+ $shade_region[$i] = 1;
+ } else {
+ $total_time += $done_array[$i];
+ $break_lines[$i] = $total_time;
+ $shade_region[$i] = 0;
+ }
+ $i++;
+ }
+ # build the 'scaled break lines' array ...
+ $i = 0;
+ while ($i < @break_lines) {
+ $scaled_break_lines[$i] = (($xupper - $margin) - ($xlower + $margin)) / $total_time * $break_lines[$i];
+ $i++;
+ }
+ # draw the processor schedule
+ $label = new Label(
+ $xlower + $margin / 2,
+ (($yupper - $yuppermargin) + ($ylower + $margin)) * 2 / 3,
+ $processor_label, 'black', 'center', 'center'
+ );
+ $pic->lb($label);
+ $label = new Label($xlower + $margin, $ylower + $margin * 3 / 4, "0", 'black', 'center', 'center');
+ $pic->lb($label);
+ for ($i = 0; $i < scalar @scaled_break_lines; $i++) {
+ $pic->moveTo($scaled_break_lines[$i], $ylower + $margin);
+ $pic->lineTo($scaled_break_lines[$i], $yupper - $yuppermargin);
+ if ($shade_region[$i] == 1) {
+ if ($i == 0) { $lower_region = 0; }
+ else { $lower_region = $scaled_break_lines[ $i - 1 ]; }
+ $label = new Label(
+ ($scaled_break_lines[$i] + $lower_region) / 2,
+ (($yupper - $yuppermargin) + ($ylower + $margin)) * 2 / 3,
+ $done_array[$i], 'black', 'center', 'center'
+ );
+ $pic->lb($label);
+ $pic->fillRegion([
+ ($scaled_break_lines[$i] + $lower_region) / 2,
+ (($yupper - $yuppermargin) + ($ylower + $margin)) * 2 / 3,
+ 'gray'
+ ]);
+ }
+ $label = new Label($scaled_break_lines[$i], $ylower + $margin * 3 / 4, $break_lines[$i], 'black', 'center',
+ 'center');
+ $pic->lb($label);
+ }
+ $pic;
+}
+
+###
+# Name : FindMinimum
+###
+
+sub FindMinimum {
+ my (@list) = @_;
+ my ($minval, $i);
+ $minval = $list[0];
+ $i = 0;
+ while ($i < scalar @list) {
+ if ($list[$i] < $minval) { $minval = $list[$i]; }
+ $i++;
+ }
+ $minval;
+}
+1;
diff --git a/macros/math/PGnauSet.pl b/macros/math/PGnauSet.pl
new file mode 100644
index 0000000000..1a4d453014
--- /dev/null
+++ b/macros/math/PGnauSet.pl
@@ -0,0 +1,346 @@
+loadMacros("PGnauGraphics.pl",);
+
+#######################################################################################################
+#
+# Name : DrawVenn2
+#
+# Input :
+#
+# $input_word = 'region1_info, region2_info, ...'
+# The information to use, arranged in order by region.
+# Use "_fill_" to shade the corresponding region, otherwise
+# the input is treated as label.
+#
+# @options = 'Arrow indicated options' in any order.
+#
+# labels=>'Set1 label, Set2 label, U'. Set1 is on the
+# left. Leaving any or all entries blank will
+# produce no label for that item. Default is
+# 'A,B,U'
+#
+# Output : $diagram = a graphics object, the diagram to plot. Use 'Plot' in the
+# .pg file.
+#
+########################################################################################################
+sub DrawVenn2 {
+ my (@input_word) = @_;
+ my ($i, $xlower, $xupper, $ylower, $yupper, $diagram, $window_scale, $axis_label, $margin, $radius,
+ $x1, $y1, $x2, $y2, $label_string, @labels, $optional_labels, $this_x, $this_y, @input, $templ,
+ $tempu, $tempr2);
+
+ # check for optional labels;
+ $i = 0;
+ while ($i < scalar @input_word - 1) {
+ if ($input_word[$i] eq "labels") {
+ $optional_labels = 1;
+ $label_string = $input_word[ $i + 1 ];
+ splice(@input_word, $i, 2);
+ $label_string =~ s/ //g;
+ @labels = split ",", $label_string;
+ }
+ $i++;
+ }
+ if (!$optional_labels) { @labels = ('A', 'B', 'U'); }
+ @input = split ",", $input_word[0];
+ for ($i = 0; $i < scalar @input; $i++) {
+ $input[$i] =~ s/ //g;
+ }
+ $margin = 5;
+ $xlower = -$margin;
+ $xupper = 100 + $margin;
+ $ylower = -$margin;
+ $yupper = 2 / 3 * (($xupper - $margin) - ($xlower + $margin)) + $margin;
+ $window_scale = 3.5;
+ $diagram = init_graph($xlower, $ylower, $xupper, $yupper,
+ 'pixels' => [ $window_scale * ($xupper - $xlower), $window_scale * ($yupper - $ylower) ]);
+ $diagram->moveTo($xlower + $margin, $ylower + $margin);
+ $diagram->lineTo($xupper - $margin, $ylower + $margin);
+ $diagram->lineTo($xupper - $margin, $yupper - $margin);
+ $diagram->lineTo($xlower + $margin, $yupper - $margin);
+ $diagram->lineTo($xlower + $margin, $ylower + $margin);
+ $axislabels = new Label(95, 7, $labels[2], 'black', 'center', 'center');
+ $diagram->lb($axislabels);
+ $radius = ($xupper - $xlower - 2 * $margin) / 4;
+ $x1 = ($xupper - $xlower - 2 * $margin) / 3;
+ $y1 = ($yupper + $ylower) / 2;
+ $x2 = ($xupper - $xlower - 2 * $margin) / 3 * 2;
+ $y2 = ($yupper + $ylower) / 2;
+
+ # draw and label the first circle
+ $templ = $x1 - $radius;
+ $tempu = $x1 + $radius;
+ $tempr2 = $radius**2;
+ $circle1 = FEQ("sqrt($tempr2-(x-$x1)^2)+$y1 for x in <$templ,$tempu> using color:red and weight:2");
+ $circle2 = FEQ("-sqrt($tempr2-(x-$x1)^2)+$y1 for x in <$templ,$tempu> using color:red and weight:2");
+ ($c1ref, $c2ref) = plot_functions($diagram, $circle1, $circle2);
+ $axislabels = new Label(
+ $x1 - (2 + sqrt(2)) / 4 * $radius,
+ $y1 + (2 + sqrt(2)) / 4 * $radius,
+ $labels[0], 'red', 'center', 'center'
+ );
+ $diagram->lb($axislabels);
+
+ # draw and label the second circle
+ $templ = $x2 - $radius;
+ $tempu = $x2 + $radius;
+ $tempr2 = $radius**2;
+ $circle1 = FEQ("sqrt($tempr2-(x-$x2)^2)+$y2 for x in <$templ,$tempu> using color:blue and weight:2");
+ $circle2 = FEQ("-sqrt($tempr2-(x-$x2)^2)+$y2 for x in <$templ,$tempu> using color:blue and weight:2");
+ ($c1ref, $c2ref) = plot_functions($diagram, $circle1, $circle2);
+ $axislabels = new Label(
+ $x2 + (2 + sqrt(2)) / 4 * $radius,
+ $y2 + (2 + sqrt(2)) / 4 * $radius,
+ $labels[1], 'blue', 'center', 'center'
+ );
+ $diagram->lb($axislabels);
+ $i = 0;
+
+ while ($i < scalar @input && $i < 4) {
+ if ($i == 0) {
+ $this_x = (($xupper - $margin) - ($xlower + $margin)) / 2;
+ $this_y = (($xupper - $margin) - ($xlower + $margin)) / 3;
+ }
+ if ($i == 1) {
+ $this_x = $x1 - sqrt(2) / 4 * $radius;
+ $this_y = $y1 + sqrt(2) / 4 * $radius;
+ }
+ if ($i == 2) {
+ $this_x = $x2 + sqrt(2) / 4 * $radius;
+ $this_y = $y2 + sqrt(2) / 4 * $radius;
+ }
+ if ($i == 3) {
+ $this_x = $x2 + (2 + sqrt(2)) / 4 * $radius;
+ $this_y = $y2 - (2 + sqrt(2)) / 4 * $radius;
+ }
+ if ($input[$i] eq "_fill_") {
+
+ $diagram->fillRegion([ $this_x, $this_y, 'gray' ]);
+ } else {
+ $axislabels = new Label($this_x, $this_y, $input[$i], 'black', 'center', 'center');
+ $diagram->lb($axislabels);
+ }
+ $i++;
+ }
+ $diagram;
+}
+
+#######################################################################################################
+#
+# Name : DrawVenn3
+#
+# Input :
+#
+# $input_word = 'region1_info, region2_info, ...'
+# The information to use, arranged in order by region.
+# Use "_fill_" to shade the corresponding region, otherwise
+# the input is treated as label.
+#
+# @options = 'Arrow indicated options' in any order.
+#
+# labels=>'Set1 label, Set2 label, Set 3 label, U'. Set1 is on the
+# left. Leaving any or all entries blank will
+# produce no label for that item. Default is
+# 'A,B,C,U'
+#
+# Output : $diagram = a graphics object, the diagram to plot. Use 'Plot' in the
+# .pg file.
+#
+########################################################################################################
+sub DrawVenn3 {
+ my (@input_word) = @_;
+ my (
+ $i, $xlower, $xupper, $ylower, $yupper, $diagram,
+ $window_scale, $axis_label, $margin, $radius, $x1, $y1,
+ $x2, $y2, $x3, $y3, $x4, $y4,
+ $label_string, @labels, $optional_labels, $this_x, $this_y, @input,
+ $templ, $tempu, $tempr2, $tempo
+ );
+
+ # check for optional labels;
+ $i = 0;
+ while ($i < scalar @input_word - 1) {
+ if ($input_word[$i] eq "labels") {
+ $optional_labels = 1;
+ $label_string = $input_word[ $i + 1 ];
+ splice(@input_word, $i, 2);
+ $label_string =~ s/ //g;
+ @labels = split ",", $label_string;
+ }
+ $i++;
+ }
+ if (!$optional_labels) { @labels = ('A', 'B', 'C', 'U'); }
+ @input = split ",", $input_word[0];
+ for ($i = 0; $i < scalar @input; $i++) {
+ $input[$i] =~ s/ //g;
+ }
+ $margin = 5;
+ $xlower = -$margin;
+ $xupper = 100 + $margin;
+ $ylower = -$margin;
+ $yupper = (4 + sqrt(3)) / 6 * (($xupper - $margin) - ($xlower + $margin)) + $margin;
+ $window_scale = 3.5;
+ $diagram = init_graph($xlower, $ylower, $xupper, $yupper,
+ 'pixels' => [ $window_scale * ($xupper - $xlower), $window_scale * ($yupper - $ylower) ]);
+ $diagram->moveTo($xlower + $margin, $ylower + $margin);
+ $diagram->lineTo($xupper - $margin, $ylower + $margin);
+ $diagram->lineTo($xupper - $margin, $yupper - $margin);
+ $diagram->lineTo($xlower + $margin, $yupper - $margin);
+ $diagram->lineTo($xlower + $margin, $ylower + $margin);
+ $axislabels = new Label(95, 7, $labels[3], 'black', 'center', 'center');
+ $diagram->lb($axislabels);
+ $radius = ($xupper - $xlower - 2 * $margin) / 4;
+ $x1 = ($xupper - $xlower - 2 * $margin) / 3;
+ $y1 = (($xupper - $margin) - ($xlower + $margin)) * (2 + sqrt(3)) / 6;
+ $x3 = ($xupper - $xlower - 2 * $margin) / 3 * 2;
+ $y3 = (($xupper - $margin) - ($xlower + $margin)) * (2 + sqrt(3)) / 6;
+ $x2 = ($xupper - $xlower - 2 * $margin) / 2;
+ $y2 = (($xupper - $margin) - ($xlower + $margin)) / 3;
+
+ # draw and label the first circle
+ $templ = $x1 - $radius;
+ $tempu = $x1 + $radius;
+ $tempr2 = $radius**2;
+ $circle1 = FEQ("sqrt($tempr2-(x-$x1)^2)+$y1 for x in <$templ,$tempu> using color:red and weight:2");
+ $circle2 = FEQ("-sqrt($tempr2-(x-$x1)^2)+$y1 for x in <$templ,$tempu> using color:red and weight:2");
+ ($c1ref, $c2ref) = plot_functions($diagram, $circle1, $circle2);
+ $axislabels = new Label(
+ $x1 - (2 + sqrt(2)) / 4 * $radius,
+ $y1 + (2 + sqrt(2)) / 4 * $radius,
+ $labels[0], 'red', 'center', 'center'
+ );
+ $diagram->lb($axislabels);
+
+ # draw and label the second circle
+ $templ = $x2 - $radius;
+ $tempu = $x2 + $radius;
+ $tempr2 = $radius**2;
+ $circle1 = FEQ("sqrt($tempr2-(x-$x2)^2)+$y2 for x in <$templ,$tempu> using color:blue and weight:2");
+ $circle2 = FEQ("-sqrt($tempr2-(x-$x2)^2)+$y2 for x in <$templ,$tempu> using color:blue and weight:2");
+ ($c1ref, $c2ref) = plot_functions($diagram, $circle1, $circle2);
+ $axislabels = new Label(
+ $x2 - (2 + sqrt(2)) / 4 * $radius,
+ $y2 - (2 + sqrt(2)) / 4 * $radius,
+ $labels[1], 'blue', 'center', 'center'
+ );
+ $diagram->lb($axislabels);
+
+ # draw and label the third circle
+ $templ = $x3 - $radius;
+ $tempu = $x3 + $radius;
+ $tempr2 = $radius**2;
+ $circle1 = FEQ("sqrt($tempr2-(x-$x3)^2)+$y3 for x in <$templ,$tempu> using color:orange and weight:2");
+ $circle2 = FEQ("-sqrt($tempr2-(x-$x3)^2)+$y3 for x in <$templ,$tempu> using color:orange and weight:2");
+ ($c1ref, $c2ref) = plot_functions($diagram, $circle1, $circle2);
+ $axislabels = new Label(
+ $x3 + (2 + sqrt(2)) / 4 * $radius,
+ $y3 + (2 + sqrt(2)) / 4 * $radius,
+ $labels[2], 'orange', 'center', 'center'
+ );
+ $diagram->lb($axislabels);
+ $x4 = $x2;
+ $y4 = $y2 + (($xupper - $margin) - ($xlower + $margin)) / (3 * sqrt(3));
+ $i = 0;
+
+ while ($i < scalar @input && $i < 8) {
+ if ($i == 0) {
+ $this_x = $x4;
+ $this_y = $y4;
+ }
+ if ($i == 1) {
+ $this_x = $x4;
+ $this_y = $y4 + (($xupper - $margin) - ($xlower + $margin)) / (6 * sqrt(3));
+ }
+ if ($i == 2) {
+ $this_x = $x1 + (($xupper - $margin) - ($xlower + $margin)) / 12;
+ $this_y = $y1 - (($xupper - $margin) - ($xlower + $margin)) * sqrt(3) / 12;
+ }
+ if ($i == 3) {
+ $this_x = $x3 - (($xupper - $margin) - ($xlower + $margin)) / 12;
+ $this_y = $y3 - (($xupper - $margin) - ($xlower + $margin)) * sqrt(3) / 12;
+ }
+ if ($i == 4) {
+ $this_x = $x3 + sqrt(2) / 4 * $radius;
+ $this_y = $y3 + sqrt(2) / 4 * $radius;
+ }
+ if ($i == 5) {
+ $this_x = $x1 - sqrt(2) / 4 * $radius;
+ $this_y = $y1 + sqrt(2) / 4 * $radius;
+ }
+ if ($i == 6) {
+ $this_x = $x2;
+ $this_y = $y2 - $radius / 2;
+ }
+ if ($i == 7) {
+ $this_x = $xupper - $margin - $radius / 2;
+ $this_y = $ylower + $margin + $radius / 2;
+ }
+ if ($input[$i] eq "_fill_") {
+ $diagram->fillRegion([ $this_x, $this_y, 'gray' ]);
+ } else {
+ $axislabels = new Label($this_x, $this_y, $input[$i], 'black', 'center', 'center');
+ $diagram->lb($axislabels);
+ }
+ $i++;
+ }
+ $diagram;
+}
+
+#####################################
+#
+# Name : Venn2answers
+#
+#
+#####################################
+sub Venn2answers {
+
+ my ($labels) = @_;
+ my (@set, $i);
+
+ @set = split ",", $labels;
+ if (scalar @set < 3) { die "Too few labels, exiting ..."; }
+ for ($i = 0; $i < scalar @set; $i++) { $set[$i] =~ s/ //g; }
+
+ $combo[0] = "0,\\( $set[0] \\cap $set[1] \\), \\( \\overline{\\overline{$set[0]} \\cup \\overline{$set[1]}} \\)";
+ $combo[1] = "1,\\( $set[0] \\cap \\overline{$set[1]}\\), \\( \\overline{\\overline{$set[0]} \\cup $set[1] } \\)";
+ $combo[2] = "2,\\( \\overline{$set[0]} \\cap $set[1]\\), \\( \\overline{$set[0] \\cup \\overline{$set[1]}} \\)";
+ $combo[3] = "3,\\( \\overline{$set[0] \\cup $set[1]}\\), \\(\\overline{$set[0]} \\cap \\overline{$set[1]} \\)";
+
+ @combo;
+}
+
+#####################################
+#
+# Name : Venn3answers
+#
+#
+#####################################
+sub Venn3answers {
+
+ my ($labels) = @_;
+ my (@set, $i);
+
+ @set = split ",", $labels;
+ if (scalar @set < 4) { die "Too few labels, exiting ..."; }
+ for ($i = 0; $i < scalar @set; $i++) { $set[$i] =~ s/ //g; }
+
+ $combo[0] =
+ "0,\\( $set[0] \\cap $set[1] \\cap $set[2] \\), \\( \\overline{\\overline{$set[0]} \\cup \\overline{$set[1]} \\cup \\overline{$set[2]}}\\), \\( \\overline{\\overline{$set[0]} \\cup \\overline{$set[1]}} \\cap $set[2] \\)";
+ $combo[1] =
+ "1,\\( $set[0] \\cap $set[2] \\cap \\overline{$set[1]} \\), \\(\\overline{\\overline{$set[0]} \\cup \\overline{$set[2]} \\cup $set[1] } \\), \\( \\overline{\\overline{$set[0]} \\cup \\overline{$set[2]}} \\cap \\overline{$set[1]} \\)";
+ $combo[2] =
+ "2,\\( $set[0] \\cap $set[1] \\cap \\overline{$set[2]} \\), \\(\\overline{\\overline{$set[0]} \\cup \\overline{$set[1]} \\cup $set[2] } \\), \\( \\overline{\\overline{$set[0]} \\cup \\overline{$set[1]}} \\cap \\overline{$set[2]} \\)";
+ $combo[3] =
+ "3,\\( $set[1] \\cap $set[2] \\cap \\overline{$set[0]} \\), \\(\\overline{\\overline{$set[1]} \\cup \\overline{$set[2]} \\cup $set[0] } \\), \\( \\overline{\\overline{$set[1]} \\cup \\overline{$set[2]}} \\cap \\overline{$set[0]} \\)";
+ $combo[4] =
+ "4,\\( $set[2] \\cap (\\overline{$set[0] \\cup $set[1]}) \\), \\( $set[2] \\cap \\overline{$set[0]} \\cap \\overline{$set[1]} \\), \\( \\overline{\\overline{$set[2]} \\cup $set[0] \\cup $set[1]} \\)";
+ $combo[5] =
+ "5,\\( $set[0] \\cap (\\overline{$set[2] \\cup $set[1]}) \\), \\( $set[0] \\cap \\overline{$set[2]} \\cap \\overline{$set[1]} \\), \\( \\overline{\\overline{$set[0]} \\cup $set[2] \\cup $set[1]} \\)";
+ $combo[6] =
+ "6,\\( $set[1] \\cap (\\overline{$set[0] \\cup $set[2]}) \\), \\( $set[1] \\cap \\overline{$set[0]} \\cap \\overline{$set[2]} \\), \\( \\overline{\\overline{$set[1]} \\cup $set[0] \\cup $set[2]} \\)";
+ $combo[7] =
+ "7,\\( \\overline{$set[0] \\cup $set[1] \\cup $set[2]} \\), \\( \\overline{$set[0]} \\cap \\overline{$set[1]} \\cap \\overline{$set[2]} \\), \\( (\\overline{$set[0] \\cup $set[1]} ) \\cap \\overline{$set[2]}\\)";
+
+ @combo;
+}
+
+1;
diff --git a/macros/math/PGnauStats.pl b/macros/math/PGnauStats.pl
new file mode 100644
index 0000000000..bad2828673
--- /dev/null
+++ b/macros/math/PGnauStats.pl
@@ -0,0 +1,1160 @@
+loadMacros("PGnauGraphics.pl",);
+
+################################
+#Name: MeanDev
+#Input: List of data values
+#Output: List containing mean and standard deviation
+################################
+sub MeanDev {
+ my (@list) = @_;
+ my ($i, $dev, $sum, $mean, $size, @values);
+
+ $sum = 0;
+ $size = scalar @list;
+ foreach $val (@list) {
+ $sum += $val;
+ }
+ $mean = $sum / $size;
+
+ $sum = 0;
+ foreach $val (@list) {
+ $sum += ($val - $mean)**2;
+ }
+ $dev = sqrt($sum / ($size - 1));
+
+ @values = ($mean, $dev);
+}
+
+################################
+#Name: Median
+#Input: List of data values
+#Output: Value of the median
+################################
+sub Median {
+ my (@list) = @_;
+
+ my ($med, $size);
+
+ @list = num_sort(@list);
+ $size = scalar @list;
+
+ if ($size % 2 == 0) {
+ $med = ($list[ $size / 2 ] + $list[ $size / 2 - 1 ]) / 2;
+ } else {
+ $med = $list[ $size / 2 ];
+ }
+}
+
+################################
+#Name: FiveNum
+#Input: List of data values
+#Output: List containing the five number summary in order from Low to High
+################################
+sub FiveNum {
+ my (@list) = @_;
+
+ my ($L, $Q1, $Q2, $Q3, $H, $val, $size, $lower, $upper, @values);
+
+ @list = num_sort(@list);
+ $size = scalar @list;
+
+ $L = $list[0];
+ $H = $list[ $size - 1 ];
+ $Q2 = Median(@list);
+ $val = $size / 2;
+ if ($size % 2 == 0) {
+ $lower = $val - 1;
+ $upper = $val;
+ } else {
+ $lower = $val - 1;
+ $upper = $val + 1;
+ }
+
+ $Q1 = Median(@list[ 0 .. $lower ]);
+ $Q3 = Median(@list[ $upper .. $size - 1 ]);
+
+ @values = ($L, $Q1, $Q2, $Q3, $H);
+}
+
+################################
+#Name: Mode
+#Input: List of data values
+#Optional Input: 'Max_Modes'=> num limits the maximum number of modes to num
+#Output: List containing the mode or the list ("None") if the number of modes
+# is more than num,
+################################
+sub Mode {
+ my (@list) = @_;
+
+ my ($max, $val, $size, $count, @modes, $preval, $max_modes);
+
+ $max_modes = scalar @list;
+
+ if ($list[0] eq 'Max_Modes') {
+ shift @list;
+ $max_modes = shift @list;
+ }
+
+ @list = num_sort(@list);
+
+ $count = -1;
+ $max = 0;
+ $preval = $list[0];
+
+ foreach $val (@list) {
+ if ($val == $preval) {
+ $count++;
+ } else {
+ $count = 0;
+ }
+ if ($count > $max) {
+ @modes = ();
+ push @modes, $val;
+ $max = $count;
+ } elsif ($count == $max) {
+ push @modes, $val;
+ }
+ $preval = $val;
+ }
+
+ if (scalar @modes > $max_modes) {
+ ("None");
+ } else {
+ @modes;
+ }
+}
+
+######################################
+#Name: isstring
+#Input: A variable
+#Output: A 1 if the variable is a string, 0 otherwise
+######################################
+sub isstring {
+ my ($val) = @_;
+
+ my ($ans, $range);
+
+ $range = join '', ('a' .. 'z', 'A' .. 'Z');
+
+ $ans = 0;
+ if (length $val == 0 || $val =~ /[$range]/) {
+ $ans = 1;
+ }
+
+ $ans;
+}
+
+#########################################################################################
+#
+# Name : BoxPlot
+# Input : $min = the minimum value in the dataset
+# $q1 = the first quartile value
+# $q2 = the second quartile (median) value
+# $q3 = the third quartile value
+# $max = the maximum value in the dataset
+# Options = array of numbers to be plotted with tickmarks,
+# perhaps interspersed with the options below
+# in any order.
+#
+# Options :: The following may be passed AFTER the five numbers
+# (together with the labels, without regard to order):
+#
+# (1) Use "wmin=>number" (no quotes really) to set the
+# lower viewing window. Default is '$min'
+#
+# (2) Use "wmax=>number" (no quotes really) to set the
+# upper viewing window. Default is '$max'
+#
+# (3) Use "horzlabels=>number" to set the approximate
+# number of horizontal labels. Default is 10.
+#
+# (4) Use "axis=>0" to hide the horizontal axis.
+# Default is to plot the axis.
+#
+# (5) Use labels such as 'a','b' and so on to label any
+# of the five numbers with the desired string.
+# Default is no labels.
+#
+# Output : $pic = a graphics object.
+#
+#####################################################################################
+sub BoxPlot {
+ my ($min, $q1, $q2, $q3, $max, @optional_labels) = @_;
+ my $pic;
+ my ($horizontal_label_count, $plotaxis, @qlabels, @nlabels, $i, $skip,
+ $xlower, $xupper, $ylower, $yupper, $rough_step, $n, $a, $rounded_winmin, @labels, $label_step,
+ $label_count, @scaledfive, $axis_labels);
+
+ #initialize to default
+ my $winmin = $min;
+ my $winmax = $max;
+ $horizontal_label_count = 10;
+ $plotaxis = 1;
+
+ @qlabels = ();
+ @nlabels = ();
+
+ while (scalar @optional_labels > 0) {
+ $val = shift @optional_labels;
+ if (isstring($val)) {
+ if ($val eq "winmin") {
+ $winmin = shift @optional_labels;
+ } elsif ($val eq "winmax") {
+ $winmax = shift @optional_labels;
+ } elsif ($val eq "horzlabels") {
+ $horizontal_label_count = shift @optional_labels;
+ if ($horizontal_label_count == 0) {
+ $horizontal_label_count = 10;
+ }
+ } elsif ($val eq "axis") {
+ $plotaxis = shift @optional_labels;
+ if ($plotaxis != 1) {
+ $plotaxis = 0;
+ }
+ } else {
+ push @qlabels, $val;
+ }
+ } else {
+ push @nlabels, $val;
+ }
+ }
+
+ # error check
+ if ($winmin >= $winmax) { die "the requested window settings are unusable"; }
+
+ # setup graph. The size is essentially fixed here.
+ $xlower = -20;
+ $xupper = 120;
+ $yupper = 20;
+
+ if ($plotaxis == 1) {
+ $ylower = -10;
+ } else {
+ $ylower = 0;
+ }
+
+ $pic = init_graph($xlower, $ylower, $xupper, $yupper,
+ 'pixels' => [ 5 * ($xupper - $xlower), 5 * ($yupper - $ylower) ]);
+
+ $winsize = $winmax - $winmin;
+
+ # determine a suitable label step size. Approximately 10 'nice' labels are needed.
+ $n = 0;
+
+ $rough_step = $winsize / $horizontal_label_count;
+
+ if ($rough_step < 1) {
+ while ($rough_step < 1) {
+ $n--;
+ $rough_step = $rough_step * 10;
+ }
+ } elsif ($rough_step > 10) {
+ while ($rough_step > 10) {
+ $n++;
+ $rough_step = $rough_step / 10;
+ }
+ }
+
+ # optimize the step size. The 'larger' is taken to avoid too many labels.
+ if ($rough_step <= 2) { $a = 2; }
+ elsif ($rough_step > 5) { $a = 10; }
+ else { $a = 5; }
+
+ # actually set the 'label_step'
+ $label_step = $a * 10**$n;
+ if ($label_step == 0) { die "Error determining the label step size"; }
+
+ # next, the upper and lower window values are adjusted to 'nice' numbers, if needed.
+ $rounded_winmin = int($winmin / $label_step) * $label_step - $label_step;
+
+ #Build the 'labels' array. There should be about 10
+ $i = 0;
+ @labels = ($rounded_winmin);
+ do {
+ $i++;
+ push @labels, $rounded_winmin + $label_step * $i;
+ } while ($rounded_winmin + $label_step * $i <= $winmax);
+
+ $label_count = $i;
+ $rounded_winmax = $labels[$i];
+
+ #scale the input data for plotting
+ push @scaledfive, ($min - $winmin) * 100 / $winsize;
+ push @scaledfive, ($q1 - $winmin) * 100 / $winsize;
+ push @scaledfive, ($q2 - $winmin) * 100 / $winsize;
+ push @scaledfive, ($q3 - $winmin) * 100 / $winsize;
+ push @scaledfive, ($max - $winmin) * 100 / $winsize;
+
+ #add the horizontal labels and axis to the picture. Labeling starts at '$lower_window'.
+ if ($plotaxis == 1) {
+
+ $pic->moveTo($xlower, 0);
+ $pic->lineTo($xupper, 0, 1);
+
+ for ($i = 0; $i <= $label_count; $i++) {
+ $xval = ($labels[$i] - $winmin) * 100 / $winsize;
+
+ if ($xval < 115 && $xval > -15) {
+ $pic->moveTo($xval, 0);
+ $pic->lineTo($xval, -1, 1);
+ $axislabels = new Label($xval, -3, $labels[$i], 'black', 'center', 'center');
+ $pic->lb($axislabels);
+ }
+ }
+ }
+
+ #add the optional labels to the picture.
+ for ($i = 0; $i < scalar @nlabels; $i++) {
+ $xval = ($nlabels[$i] - $winmin) * 100 / $winsize;
+
+ if ($nlabels[$i] >= $winmin && $nlabels[$i] <= $winmax) {
+ $pic->moveTo($xval, 0);
+ $pic->lineTo($xval, 1, 1);
+ $axislabels = new Label($xval, 5, $nlabels[$i], 'black', 'center', 'center');
+ $pic->lb($axislabels);
+ }
+ }
+
+ $center = $yupper / 2;
+
+ #add the optional quartile labels
+ for ($i = 0; $i < scalar @qlabels && $i < 5; $i++) {
+ if ($qlabels[$i] ne '') {
+ $pic->moveTo($scaledfive[$i], $center + 4);
+ $pic->lineTo($scaledfive[$i], $center + 5, 1);
+ $axislabels = new Label($scaledfive[$i], $center + 8, $qlabels[$i], 'black', 'center', 'center');
+ $pic->lb($axislabels);
+ }
+ }
+
+ # draw data lines
+ $pic->stamps(closed_circle($scaledfive[0], $center, "black"));
+ $pic->moveTo($scaledfive[0], $center);
+ $pic->lineTo($scaledfive[1], $center, 1);
+ $pic->moveTo($scaledfive[3], $center, 1);
+ $pic->lineTo($scaledfive[4], $center, 1);
+ $pic->stamps(closed_circle($scaledfive[4], $center, "black"));
+
+ # draw quart boxes
+ $pic->moveTo($scaledfive[1], $center - 2);
+ $pic->lineTo($scaledfive[3], $center - 2, 1);
+ $pic->lineTo($scaledfive[3], $center + 2, 1);
+ $pic->lineTo($scaledfive[1], $center + 2, 1);
+ $pic->lineTo($scaledfive[1], $center - 2, 1);
+
+ # draw median lines
+ $pic->moveTo($scaledfive[2], $center - 2);
+ $pic->lineTo($scaledfive[2], $center + 2, 1);
+ $pic;
+}
+
+#################
+# Name : SpecialData
+# Input : A string containing the type of data desired (Exp, Sym, Bi, Norm, Left, Right)
+# and any optional input.
+# Optional Input : count => n : the number of data values to generate (default = 500)
+# mean => n : The mean for the normal, left or right distributions (default = 0)
+# dev => n : The standard deviation for the normal, left or right distributions (default = 1)
+# min => n : The minimum value for the data (default = 0)
+# max => n : The maximum value for the data (default = 100)
+# Output : A list of data values with the special properties.
+#################
+
+sub SpecialData {
+ my ($type, %options) = @_;
+
+ my ($x, $sd, @dat, $max, $min, $mean, $count, $range);
+
+ my ($B, $B1, $B2);
+ @dat = ();
+
+ $type = lc $type;
+
+ if (defined $options{count}) { $count = $options{count}; }
+ else { $count = 500; }
+ if (defined $options{mean}) { $mean = $options{mean}; }
+ else { $mean = 0; }
+ if (defined $options{dev}) { $sd = $options{dev}; }
+ else { $sd = 1; }
+ if (defined $options{min}) { $min = $options{min}; }
+ else { $min = 0 }
+ if (defined $options{max}) { $max = $options{max}; }
+ else { $max = 100; }
+
+ $range = $max - $min;
+
+ if ($type eq 'exp') {
+ $B = random(.7, 10, .05);
+ for ($i = 0; $i < $count; $i++) {
+ $val = random(0, .99, .01);
+ push @dat, -$B * ln(1 - $val);
+ }
+ } elsif ($type eq 'bi') {
+ $B1 = random(0, $range / 5, 1);
+ $B2 = random(9 * $range / 10, $range, 1);
+
+ for ($i = 0; $i < $count / 5; $i++) {
+ push @dat, $min + random($B1 - $range / 10, $B1 + $range / 10, 1);
+ }
+ for ($i = 0; $i < 2 * $count / 5; $i++) {
+ push @dat, $min + random($B2 - $range / 10, $B2 + $range / 10, 1);
+ }
+ for ($i = 0; $i < $count; $i++) {
+ push @dat, random($min, $max, 1);
+ }
+ } else {
+ for ($i = 0; $i < $count; $i++) {
+ $val = RandomNormalNumber(mean => $mean, dev => $sd);
+ push @dat, $val;
+ }
+
+ if ($type eq 'norm' || $type eq 'sym') {
+ @dat = num_sort(@dat);
+ $low = $dat[0];
+ $high = $dat[ $count - 1 ];
+ $diff = $high - $low;
+ for ($i = 0; $i < $count; $i++) {
+ $dat[$i] = $range * ($dat[$i] - $low) / $diff + $min;
+ }
+ } else {
+ for ($i = 0; $i < $count; $i++) {
+ $dat[$i] = $dat[$i]**2;
+ }
+ @dat = num_sort(@dat);
+ $low = $dat[0];
+ $high = $dat[ $count - 1 ];
+ $diff = $high - $low;
+ for ($i = 0; $i < $count; $i++) {
+ $dat[$i] = $range * ($dat[$i] - $low) / $diff + $min;
+ if ($type eq 'left') {
+ $dat[$i] = -$dat[$i] + $min + $max;
+ }
+ }
+ }
+ }
+
+ @dat;
+}
+
+######################################
+#Name: PercentStemAndLeaf
+#Input: List of percentage values (anything from 0 to 100).
+#Optional Input: At beginning of list:
+# all => 1,0 turns on or off the display of all columns of the
+# table (useful if data is within a small range).
+# sort => 1,0 turns on or off sorted data rows.
+# single=>1,0 displays answers as a single cell in the table or
+# determines the number from the data.
+#Output: A list containing two strings. The first is a string that represents
+# the html table to be displayed and the second is the TeX table to be
+# displayed.
+######################################
+sub PercentStemAndLeaf {
+ my (@dat) = @_;
+
+ my ($i, $j, $s1, $s2, $all, $low, $max, $num, $row, $cols, $high, @list, $sort, $var1, $var2,, $tex_table,
+ $html_table);
+
+ while (isstring($dat[0]) == 1) {
+ $var1 = shift @dat;
+ if ($var1 eq 'all') {
+ $all = shift @dat;
+ } elsif ($var1 eq 'sort') {
+ $sort = shift @dat;
+ } elsif ($var1 eq 'single') {
+ if (shift @dat == 0) {
+ $cols = -1;
+ }
+ } else {
+ shift @dat;
+ }
+ }
+
+ $all = 1 unless defined($all);
+ $sort = 1 unless defined($sort);
+ $cols = 3 unless defined($cols);
+
+ for ($i = 0; $i < scalar @dat; $i++) {
+ $num = int($dat[$i] / 10);
+ $row = "row$num";
+ push @$row, $dat[$i] % 10;
+ }
+
+ $max = 0;
+ for ($i = 0; $i <= 10; $i++) {
+ $j = 10 - $i;
+ $var1 = "row$i";
+ $var2 = "row$j";
+ $s1 = scalar @$var1;
+ $s2 = scalar @$var2;
+ if ($s1 != 0) { $high = $i; }
+ if ($s2 != 0) { $low = $j; }
+ if ($s1 > $max) { $max = $s1; }
+ }
+ if ($all) {
+ $low = 0;
+ $high = 10;
+ }
+ if ($cols == -1) {
+ $cols = $max;
+ }
+ $tex_cols = $cols - 1;
+
+ $align = 'r|';
+ for ($i = 0; $i < $tex_cols - 1; $i++) {
+ $align .= 'l';
+ }
+
+ $html_table = begintable($cols);
+ $tex_table = "\\begin{tabular}{$align}";
+
+ for ($i = $high; $i >= $low; $i--) {
+ $var1 = "row$i";
+
+ @list = @$var1;
+ if ($sort) {
+ @list = num_sort(@list);
+ }
+ if ($cols == 3) {
+ @list = (join '', @list);
+ }
+ $html_table .= row($i, ' ', @list);
+ $tex_table .= "\n $i";
+ for ($j = 1; $j < $tex_cols; $j++) {
+ if (scalar @list > 0) {
+ $var2 = shift @list;
+ } else {
+ $var2 = '';
+ }
+ $tex_table .= "\&$var2 ";
+ }
+ $tex_table .= "\\\\\n";
+ }
+ $html_table .= endtable();
+ $tex_table .= '\end{tabular}';
+ ($html_table, $tex_table);
+}
+
+###
+# Name : RoundStep
+###
+sub RoundStep {
+ my ($num, $val) = @_;
+ my ($remainder);
+ #qualify the input
+ $minus = 0;
+ if ($num < 0) { $num = -$num; $minus = 1; }
+ $val = abs($val);
+ $remainder = ($num / $val - int($num / $val)) * $val;
+ if ($remainder < ($val / 2)) {
+ $num = int($num / $val) * $val;
+ } else {
+ $num = (int($num / $val) + 1) * $val;
+ }
+ if ($minus) { $num = -$num; }
+ $num;
+}
+
+###
+# Name : CeilingStep
+###
+sub CeilingStep {
+ my ($num, $val) = @_;
+ #qualify the input
+ $minus = 0;
+ if ($num < 0) { $num = -$num; $minus = 1; }
+ $val = abs($val);
+ $num = (int($num / $val) + 1) * $val;
+ if ($minus) { $num = -$num; }
+ $num;
+}
+
+###
+# Name : FloorStep
+###
+sub FloorStep {
+ my ($num, $val) = @_;
+ #qualify the input
+ $minus = 0;
+ if ($num < 0) { $num = -$num; $minus = 1; }
+ $val = abs($val);
+ $num = int($num / $val) * $val;
+ if ($minus) { $num = -$num; }
+ $num;
+}
+
+#############################################################################
+# Name : Histogram
+# Input : @data = "the data"
+# Options = may be interspersed in the data in any order.
+#
+# Options :: The following may be passed AFTER the standard input
+# above (without regard to order):
+#
+# (1) Use "labelcells=>1" (no quotes really) to include
+# frequency labels at the top of each cell.
+# The default is no labels.
+#
+# (2) Use "labelcount=>number" (no quotes really) to
+# set an approximate number of labels to use on the
+# vertical axis. Default is 5.
+#
+# (3) Use "title=>string" to add a title, centered at
+# the top.
+#
+# (4) Use "axislabel=>string" to add an axis label,
+# centered at the bottom.
+#
+# (5) Use "bins=>number" to specify approximately how
+# many bins should be used. Default is 10.
+#
+# Note : any extra numbers input at the end will be
+# taken as data.
+#
+# Output : $pic = a graphics object
+#############################################################################
+sub Histogram {
+ my (@data) = @_;
+ my $pic;
+ my (
+ $key, $cell_labels, $target_label_count, $xlower,
+ $xupper, $ylower, $yupper, @division_values,
+ $frequency, $j, @frequencies, $maxfreq,
+ @scaled_division_values, $division_count, $axis_labels, $rough_step,
+ $n, $step, $scaled_step, $this_label,
+ $i, $bins
+ );
+ $key = 0;
+
+ # check for options
+ for ($i = 0; $i < scalar @data; $i++) {
+ $cut = 0;
+ $val = $data[$i];
+ if ($val eq 'labelcells') {
+ if ($data[ $i + 1 ] != 0) { $cell_labels = 1; }
+ $cut = 1;
+ } elsif ($val eq 'labelcount') {
+ $target_label_count = $data[ $i + 1 ];
+ $cut = 1;
+ } elsif ($val eq 'title') {
+ $title = $data[ $i + 1 ];
+ $cut = 1;
+ } elsif ($val eq 'axislabel') {
+ $axislabel = $data[ $i + 1 ];
+ $cut = 1;
+ } elsif ($val eq 'bins') {
+ $bins = $data[ $i + 1 ];
+ $cut = 1;
+ }
+
+ if ($cut == 1) {
+ splice(@data, $i, 2);
+ $i--;
+ }
+ }
+
+ $cell_labels = 0 unless defined($cell_labels);
+ $target_label_count = 5 unless defined($target_label_count);
+ $title = '' unless defined($title);
+ $axislabel = '' unless defined($axislabel);
+ $bins = 10 unless defined($bins);
+
+ # setup graph. The size is essentially fixed here.
+ $window_size = 5;
+ $xlower = -20;
+ $xupper = 120;
+ $ylower = -20;
+ $yupper = 70;
+ $pic = init_graph($xlower, $ylower, $xupper, $yupper,
+ 'pixels' => [ $window_size * ($xupper - $xlower), $window_size * ($yupper - $ylower) ]);
+ # find the data range
+ $min = min(@data);
+ $max = max(@data);
+ $rough_step = ($max - $min) / $bins;
+ #find a 'nice' width
+ $n = 0;
+
+ if (int($rough_step) > 10) {
+ while ($rough_step > 10) {
+ $n++;
+ $rough_step = $rough_step / 10;
+ }
+ } elsif (int($rough_step) < 1) {
+ while ($rough_step < 1) {
+ $n--;
+ $rough_step = $rough_step * 10;
+ }
+ }
+
+ if ($rough_step <= 2) { $a = 2; }
+ elsif ($rough_step > 2 && $rough_step <= 5) { $a = 5; }
+ else { $a = 10; }
+ $width = $a * 10**$n;
+ # round
+ $min = FloorStep($min, $width);
+ $max = CeilingStep($max, $width);
+ # setup the cell division values
+ @division_values = ($min);
+ $i = 0;
+ while ($min + $width * $i < $max) {
+ $i++;
+ push @division_values, $min + $width * $i;
+ }
+ $division_count = $i;
+ # calculate the frequencies for each cell
+ $maxfreq = 1;
+ for ($i = 0; $i < $division_count; $i++) {
+ #determine the current frequency
+ $j = 0;
+ $frequency = 0;
+ while ($j < scalar @data) {
+ if ($data[$j] >= $division_values[$i] && $data[$j] < $division_values[ $i + 1 ]) {
+ $frequency++;
+ }
+ $j++;
+ }
+ push @frequencies, $frequency;
+ if ($frequencies[$i] > $maxfreq) {
+ $maxfreq = $frequencies[$i];
+ }
+ }
+ #scaling transformations. The given $min and $max are used for this.
+ $i = 0;
+ while ($i <= $division_count) {
+ $scaled_division_values[$i] = ($division_values[$i] - $min) * 100 / ($max - $min);
+ if ($i != $division_count) {
+ $scaled_frequencies[$i] = ($frequencies[$i]) * 50 / ($maxfreq);
+ }
+ $i++;
+ }
+ $pic->moveTo($scaled_division_values[0], 0);
+ $pic->lineTo($scaled_division_values[$division_count], 0, 1);
+ $pic->lineTo($scaled_division_values[$division_count], 55, 1);
+ $pic->lineTo($scaled_division_values[0], 55, 1);
+ $pic->lineTo($scaled_division_values[0], 0, 1);
+ #label the division points, and draw the cells
+ $i = 0;
+ while ($i < $division_count) {
+ $pic->moveTo($scaled_division_values[$i], 0);
+ $axislabels = new Label($scaled_division_values[$i], -3, $division_values[$i], 'black', 'center', 'center');
+ $pic->lb($axislabels);
+ $pic->lineTo($scaled_division_values[$i], $scaled_frequencies[$i], 1);
+ $pic->lineTo($scaled_division_values[ $i + 1 ], $scaled_frequencies[$i], 1);
+ $pic->lineTo($scaled_division_values[ $i + 1 ], 0, 1);
+ if ($frequencies[$i] > 0) {
+ $pic->fillRegion(
+ [ ($scaled_division_values[$i] + $scaled_division_values[ $i + 1 ]) / 2, 0.000001, 'gray' ]);
+ }
+ #add the optional cell labels
+ if ($cell_labels == 1) {
+ if ($frequencies[$i] > 0) {
+ $axislabels = new Label(
+ ($scaled_division_values[$i] + $scaled_division_values[ $i + 1 ]) / 2,
+ $scaled_frequencies[$i] + 3,
+ $frequencies[$i], 'black', 'center', 'center'
+ );
+ $pic->lb($axislabels);
+ }
+ }
+ $i++;
+ }
+ # add the last division label if it is not too far to the right
+ if ($scaled_division_values[$division_count] <= 110) {
+ $pic->moveTo($scaled_division_values[$division_count], 0);
+ $axislabels = new Label(
+ $scaled_division_values[$division_count],
+ -3, $division_values[$division_count],
+ 'black', 'center', 'center'
+ );
+ $pic->lb($axislabels);
+ }
+ #find 'nice' frequency axis labels
+ $n = 0;
+ $rough_step = $maxfreq / $target_label_count;
+ if (int($rough_step) > 10) {
+ while ($rough_step > 10) {
+ $n++;
+ $rough_step = $rough_step / 10;
+ }
+ } elsif (int($rough_step) < 1) {
+ $rough_step = -1;
+ }
+
+ if ($rough_step < 0) { $a = 1; }
+ elsif ($rough_step > 0 && $rough_step <= 2) { $a = 2; }
+ elsif ($rough_step > 2 && $rough_step <= 5) { $a = 5; }
+ else { $a = 10; }
+
+ $step = $a * 10**$n;
+ $scaled_step = $step * 50 / $maxfreq;
+ # add the tickmarks and labels
+ $i = 1;
+ while ($i <= $target_label_count && $scaled_step * $i < 55) {
+ #draw tick marks
+ $pic->moveTo($scaled_division_values[0], $scaled_step * $i);
+ $pic->lineTo($scaled_division_values[0] - 1, $scaled_step * $i);
+ #create this label
+ $this_label = $step * $i;
+ $axislabels =
+ new Label($scaled_division_values[0] - 3, $scaled_step * $i + 1.25, $this_label, 'black', 'center',
+ 'center');
+ $pic->lb($axislabels);
+ $i++;
+ }
+ # add the title, if needed.
+ if ($title ne '') {
+ $axislabels = new Label(50, ($yupper + 50) / 2, $title, 'black', 'center', 'center');
+ $pic->lb($axislabels);
+ }
+ # add bottom label, if needed.
+ if ($axislabel ne '') {
+ $axislabels = new Label(50, $ylower / 2, $axislabel, 'black', 'center', 'center');
+ $pic->lb($axislabels);
+ }
+ $pic;
+}
+
+################################
+#Name: RandomNormalNumber
+#Input: None required
+#Optional Input: 'mean' => the mean of the normal distribution being used. (default = 0)
+# 'dev' => the standard deviation of the normal distribution being used. (default = 1)
+#Output: A number that is related to the standard normal curve with the given mean and deviation.
+################################
+sub RandomNormalNumber {
+ my (@options) = @_;
+
+ my ($dev, $val, $mean, $norm_num);
+
+ while (scalar @options > 0) {
+ $val = shift @options;
+ if ($val eq 'mean') {
+ $mean = shift @options;
+ } elsif ($val eq 'dev') {
+ $dev = shift @options;
+ } else {
+ shift @options;
+ }
+ }
+ $mean = 0 unless defined($mean);
+ $dev = 1 unless defined($dev);
+
+ $num = random(.01, .99, .01);
+ if ($num >= .5) {
+ $norm_num = normal_distr($num - .5, mean => $mean, deviation => $dev);
+ } else {
+ $norm_num = 2 * $mean - normal_distr(.5 - $num, mean => $mean, deviation => $dev);
+ }
+ $norm_num;
+}
+
+################################
+#Name: Scatterplot
+#Input: Correlation coefficient
+#Optional Input: 'count'=> Number of data points to be calculated.
+# 'mean' => the mean of the normal distribution being used.
+# 'dev' => the standard deviation of the normal distribution being used.
+# 'slope'=> the slope of the scatterplot.
+#Output: A picture of the scatterplot with given slope and correlation coefficient.
+################################
+sub Scatterplot {
+ my ($corr, @options) = @_;
+
+ my ($i, $y1, $y2, $dev, $max, $min, $pic, $val, $mean, $circle, $count, @x_vals, @y_vals);
+
+ while (scalar @options > 0) {
+ $val = shift @options;
+ if ($val eq 'count') {
+ $count = shift @options;
+ } elsif ($val eq 'mean') {
+ $mean = shift @options;
+ } elsif ($val eq 'dev') {
+ $dev = shift @options;
+ } elsif ($val eq 'slope') {
+ $slope = shift @options;
+ } else {
+ shift @options;
+ }
+ }
+
+ $count = 100 unless defined($count);
+ $mean = 0 unless defined($mean);
+ $dev = 1 unless defined($dev);
+ $slope = 1 unless defined($slope);
+
+ $min = 0;
+ $max = 2;
+ for ($i = 0; $i < $count; $i++) {
+ $val = RandomNormalNumber('mean' => $mean, 'dev' => $dev);
+ $y1 = RandomNormalNumber('mean' => $mean, 'dev' => $dev);
+ $y2 = sqrt(1 - $corr**2) * $val + $slope * $corr * $y1;
+ if ($y1 < $min) {
+ $min = $y1;
+ } elsif ($y1 > $max) {
+ $max = $y1;
+ }
+ if ($y2 < $min) {
+ $min = $y2;
+ } elsif ($y2 > $max) {
+ $max = $y2;
+ }
+ push @x_vals, $y1;
+ push @y_vals, $y2;
+ }
+
+ $min = $min - 1;
+ $max = $max + 1;
+ $pic = "";
+ $pic = init_graph($min, $min, $max, $max, 'pixels' => [ 200, 200 ]);
+ for ($i = 0; $i < $count; $i++) {
+ $circle = new Circle($x_vals[$i], $y_vals[$i], 2, 'black', 'none');
+ $pic->stamps($circle);
+ }
+ $pic;
+}
+
+######################################
+#Name: PieChart
+#Input: List of values as: decimals - which should add to 1. If it
+# sums to less than one, the function will make up the difference.
+# or numbers > 1 - will be treated as numerators for fractions. The sum of the
+# numbers will be the denominator and fractions will be placed on the
+# circle plot.
+#Optional Input: percent => 0, 1 : Off or on display of percentage values (regardless of input manner) (default = 0)
+# labels => 1, 0 : On or off label display. (default = 1)
+# values => 1, 0 : On or off value display. (default = 1)
+#Output: A picture of the corresponding circle plot
+######################################
+sub PieChart {
+ my @list = @_;
+
+ my (
+ $i, @R, @G, @B, $v1, $v2, $Bot, $lab,
+ $pic, $Top, $val, $mark, $angle, $denom, $total, $value,
+ $labels, $radius, @values, $percent, $lett_adj, $line_adj
+ );
+
+ while (isstring($list[0])) {
+ $val = shift @list;
+ if ($val eq 'percent') {
+ $percent = shift @list;
+ } elsif ($val eq 'labels') {
+ $labels = shift @list;
+ } elsif ($val eq 'values') {
+ $value = shift @list;
+ } else {
+ shift @list;
+ }
+ }
+ $labels = 1 unless defined($labels);
+ $value = 1 unless defined($values);
+ $percent = 0 unless defined($percent);
+
+ $total = 0;
+ foreach $val (@list) {
+ $total += $val;
+ }
+
+ if ($total <= 0) { return "Incorrect values" }
+ if ($total > 2) {
+ $denom = $total;
+ } elsif ($total < 1) {
+ push @list, 1 - $total;
+ $denom = 1;
+ } else {
+ $denom = 1;
+ }
+
+ foreach $val (@list) {
+ if ($percent == 1 && $total < 2) {
+ $lab = 100 * $val;
+ push @values, "$lab%";
+ } elsif ($percent == 1) {
+ $lab = 100 * $val / $total;
+ push @values, "$lab%";
+ } elsif ($denom != 1) {
+ $i = gcd($val, $total);
+ $v1 = $val / $i;
+ $v2 = $denom / $i;
+ push @values, "$v1/$v2";
+ } else {
+ push @values, $val;
+ }
+ push @radlist, $val / $total * 2 * $PI;
+ }
+
+ $radius = 25;
+ $v1 = -3 / 2 * $radius;
+ $v2 = 3 / 2 * $radius;
+ $val = $radius**2;
+ $pic = init_graph($v1, $v1, $v2, $v2, 'pixels' => [ 300, 300 ]);
+ $Top = FEQ("sqrt($val-(x)^2) for x in <-$radius,$radius> using color:blue and weight:2");
+ $Btm = FEQ("-sqrt($val-(x)^2) for x in <-$radius,$radius> using color:blue and weight:2");
+ plot_functions($pic, $Top, $Btm);
+
+ $angle = 0;
+ $total = scalar @list;
+ $dc = int(255 / ($total + 1));
+ for ($i = 1; $i <= $total; $i++) {
+ push @R, 255 - $i * $dc;
+ push @B, $i * $dc;
+ push @G, 0;
+ }
+ $i = 0;
+ foreach $val (@radlist) {
+ $pic->moveTo(0, 0);
+ $pic->lineTo($radius * cos($angle), $radius * sin($angle), 1);
+ $angle += $val;
+ $mark = $angle - $val / 2;
+ $v1 = $radius * cos($mark);
+ $v2 = $radius * sin($mark);
+ $lett_adj = 2 / 3;
+ $line_adj = 7 / 8;
+ $pic->new_color("col$i", $R[$i], $G[$i], $B[$i]);
+ $pic->fillRegion([ $v1 / 2, $v2 / 2, "col$i" ]);
+
+ if ($value == 1) {
+ $lab = new Label(1.3 * $v1, 1.3 * $v2, "$values[$i]", 'black', 'center', 'center');
+ $pic->lb($lab);
+ $pic->moveTo($line_adj * $v1, $line_adj * $v2);
+ $pic->lineTo(1.1 * $v1, 1.1 * $v2, 1);
+ }
+ if ($labels == 1) {
+ $lab = new Label($lett_adj * $v1, $lett_adj * $v2, $ALPHABET[ $i % 26 ], 'yellow', 'center', 'center');
+ $pic->lb($lab);
+ }
+ $i++;
+ }
+
+ $pic;
+}
+
+###########################################################################################################
+#
+# Name : DrawNormalDist
+#
+# Input : lower_z_point, upper_z_point (use -INF or INF for infinite values)
+#
+# Optional Input : After first two points.
+# $lower_label = the lower label to use. Default is 'a'
+# $upper_label = the upper label to use. Default is 'b'
+# (if only want upper label, must input a lower label)
+# outside => n - 0, 1 to shade between or outside the numbers Default = 0;
+# title => 'title string' - will display a title if desired.
+# mean => n - will display the given mean (default display nothing)
+#
+###########################################################################################################
+sub DrawNormalDist {
+ my ($low, $high, @opt) = @_;
+
+ my (
+ $x1, $x2, $y1, $y2, $pic, $x_val, $y_lab,
+ $y_min, $y_tit, $y_val, $scale, $margin, $xlower, $xupper,
+ $ylower, $yupper, $low_pt, $high_pt, $title, $label, $outside,
+ $trace, $x_slope, $x_int, $y_slope, $y_int, @pt_labels, $mean_val
+ );
+
+ ###########################
+ # begin intialize and setup
+ # initialize to default
+ # check for options
+ while (@opt) {
+ $val = shift @opt;
+ if ($val eq 'outside') { $outside = shift @opt; }
+ elsif ($val eq 'title') { $title = shift @opt; }
+ elsif ($val eq 'mean') { $mean_val = shift @opt; }
+ else { push @pt_labels, $val; }
+ }
+ if (scalar @pt_labels == 0) { push @pt_labels, 'a', 'b'; }
+ elsif (scalar @pt_labels == 1) { push @pt_labels, 'b'; }
+ $outside = 0 unless defined($outside);
+ $title = '' unless defined($title);
+
+ if ($low eq '-INF') { $low_pt = -3.5; }
+ else { $low_pt = $low; }
+ if ($high eq 'INF') { $high_pt = 3.5; }
+ else { $high_pt = $high; }
+ # end initialize and setup
+ ##########################
+
+ # setup the graph. The size is essentially fixed here.
+ $scale = 4;
+ $margin = 5;
+ $xlower = -$margin;
+ $xupper = 100 + $margin;
+ $ylower = -$margin;
+ $yupper = 50 + $margin;
+ $x1 = $xlower + $margin;
+ $x2 = $xupper - $margin;
+ $y1 = $ylower + $margin + 2.5;
+ $y2 = $yupper - $margin + 2.5;
+
+ $pic = init_graph($xlower, $ylower, $xupper, $yupper,
+ 'pixels' => [ $scale * ($xupper - $xlower), $scale * ($yupper - $ylower) ]);
+ # $pic->moveTo($x1, $y1);
+ # $pic->lineTo($x2, $y1, 1);
+ # $pic->lineTo($x2, $y2, 1);
+ # $pic->lineTo($x1, $y2, 1);
+ # $pic->lineTo($x1, $y1, 1);
+
+ $x_slope = ($x2 - $x1 - 2 * $margin) / 6;
+ $x_int = $x1 + $margin + $x_slope * 3;
+ $y_slope = ($y2 - $y1 - 2 * $margin) * sqrt(2 * $PI);
+ $y_int = $y1 + $margin;
+ $y_min = $y1 + $margin;
+
+ $trace = FEQ(
+ "1/sqrt(2*$PI)*exp(-((x-$x_int)/$x_slope)^2/2)*$y_slope + $y_int for x in <$x1, $x2> using color:black and weight:1"
+ );
+ plot_functions($pic, $trace);
+
+ $pic->moveTo($x1, $y_min);
+ $pic->lineTo($x2, $y_min);
+
+ # add labels to the independent axis
+ $y_lab = $y1 + 3 * $margin / 4;
+
+ $x_val = $low_pt * $x_slope + $x_int;
+ $y_val = 1 / sqrt(2 * $PI) * exp(-($low_pt)**2 / 2) * $y_slope + $y_int;
+ $label = new Label($x_val, $y_lab, $pt_labels[0], 'black', 'center', 'center');
+ $pic->lb($label);
+ $pic->moveTo($x_val, $y_min);
+ $pic->lineTo($x_val, $y_min - 1, 1);
+ $pic->moveTo($x_val, $y_min);
+ $pic->lineTo($x_val, $y_val, 'gray');
+
+ $x_val = $high_pt * $x_slope + $x_int;
+ $y_val = 1 / sqrt(2 * $PI) * exp(-($high_pt)**2 / 2) * $y_slope + $y_int;
+ $label = new Label($x_val, $y_lab, $pt_labels[1], 'black', 'center', 'center');
+ $pic->lb($label);
+ $pic->moveTo($x_val, $y_min);
+ $pic->lineTo($x_val, $y_min - 1, 1);
+ $pic->moveTo($x_val, $y_min);
+ $pic->lineTo($x_val, $y_val, 'gray');
+
+ if (defined $mean_val) {
+ $x_val = ($xupper + $xlower) / 2;
+ $pic->moveTo($x_val, $y_min);
+ $pic->lineTo($x_val, $y_min - 1, 1);
+ $label = new Label($x_val, $y_lab, $mean_val, 'black', 'center', 'center');
+ $pic->lb($label);
+ }
+ if ($title ne '') {
+ $y_tit = $y_lab - $margin - 1;
+ $y_val = $y_lab - $margin + 1;
+ $x_val = ($xupper + $xlower) / 2;
+ $label = new Label($x_val, $y_tit, $title, 'black', 'center', 'center');
+ $pic->lb($label);
+ }
+
+ # shading
+ if ($outside == 1) {
+ $pic->fillRegion([ ($low_pt - 3.25) / 2 * $x_slope + $x_int, $y_min + 0.0001, 'gray' ]);
+ $pic->fillRegion([ ($high_pt + 3.25) / 2 * $x_slope + $x_int, $y_min + 0.0001, 'gray' ]);
+ } elsif ($high_pt - $low_pt > 0.05) {
+ $pic->fillRegion([ ($high_pt + $low_pt) / 2 * $x_slope + $x_int, $y_min + 0.0001, 'gray' ]);
+ }
+ $pic;
+}
+
+1;
diff --git a/macros/PGnumericalmacros.pl b/macros/math/PGnumericalmacros.pl
similarity index 100%
rename from macros/PGnumericalmacros.pl
rename to macros/math/PGnumericalmacros.pl
diff --git a/macros/PGnumericevaluators.pl b/macros/math/PGnumericevaluators.pl
similarity index 100%
rename from macros/PGnumericevaluators.pl
rename to macros/math/PGnumericevaluators.pl
diff --git a/macros/PGpolynomialmacros.pl b/macros/math/PGpolynomialmacros.pl
similarity index 100%
rename from macros/PGpolynomialmacros.pl
rename to macros/math/PGpolynomialmacros.pl
diff --git a/macros/PGstatisticsmacros.pl b/macros/math/PGstatisticsmacros.pl
similarity index 100%
rename from macros/PGstatisticsmacros.pl
rename to macros/math/PGstatisticsmacros.pl
diff --git a/macros/math/SI_property_tables.pl b/macros/math/SI_property_tables.pl
new file mode 100644
index 0000000000..f44824b615
--- /dev/null
+++ b/macros/math/SI_property_tables.pl
@@ -0,0 +1,460 @@
+# SI_property_tables.pl
+# Rename this file (with .pl extension) and place it in your course macros directory,
+
+@SI_property_tables_names = (
+ "SI_water_Tsat ", "SI_water_Psat ", "SI_water_vf ", "SI_water_vfg ", "SI_water_vg ", "SI_water_uf ",
+ "SI_water_ufg ", "SI_water_ug ", "SI_water_hf ", "SI_water_hfg ", "SI_water_hg ", "SI_water_sf ",
+ "SI_water_sfg ", "SI_water_sg ",
+
+ "SI_air_T ", "SI_air_h ", "SI_air_Pr ", "SI_air_u ", "SI_air_vr ", "SI_air_so ",
+
+ "SI_superWater_0p1MPa_T ", "SI_superWater_0p1MPa_v ", "SI_superWater_0p1MPa_u ", "SI_superWater_0p1MPa_h ",
+ "SI_superWater_0p1MPa_s ",
+
+ "SI_superWater_1p2MPa_T ", "SI_superWater_1p2MPa_v ", "SI_superWater_1p2MPa_u ", "SI_superWater_1p2MPa_h ",
+ "SI_superWater_1p2MPa_s ",
+
+ "SI_superWater_1p4MPa_T ", "SI_superWater_1p4MPa_v ", "SI_superWater_1p4MPa_u ", "SI_superWater_1p4MPa_h ",
+ "SI_superWater_1p4MPa_s ",
+
+ "SI_superWater_1p6MPa_T ", "SI_superWater_1p6MPa_v ", "SI_superWater_1p6MPa_u ", "SI_superWater_1p6MPa_h ",
+ "SI_superWater_1p6MPa_s ",
+
+ "SI_superWater_1p8MPa_T ", "SI_superWater_1p8MPa_v ", "SI_superWater_1p8MPa_u ", "SI_superWater_1p8MPa_h ",
+ "SI_superWater_1p8MPa_s ",
+
+ "SI_superWater_10MPa_T ", "SI_superWater_10MPa_v ", "SI_superWater_10MPa_u ", "SI_superWater_10MPa_h ",
+ "SI_superWater_10MPa_s ",
+
+ "SI_R134a_Tsat ", "SI_R134a_Psat ", "SI_R134a_vf ", "SI_R134a_vfg ", "SI_R134a_vg ", "SI_R134a_uf ",
+ "SI_R134a_ufg ", "SI_R134a_ug ", "SI_R134a_hf ", "SI_R134a_hfg ", "SI_R134a_hg ", "SI_R134a_sf ",
+ "SI_R134a_sfg ", "SI_R134a_sg ",
+
+ "SI_superR134a_1p2MPa_T ", "SI_superR134a_1p2MPa_v ", "SI_superR134a_1p2MPa_u ", "SI_superR134a_1p2MPa_h ",
+ "SI_superR134a_1p2MPa_s ",
+
+ "SI_superR134a_1p2MPa_T ", "SI_superR134a_1p2MPa_v ", "SI_superR134a_1p2MPa_u ", "SI_superR134a_1p2MPa_h ",
+ "SI_superR134a_1p2MPa_s ",
+
+ "SI_superR134a_1p2MPa_T ", "SI_superR134a_1p2MPa_v ", "SI_superR134a_1p2MPa_u ", "SI_superR134a_1p2MPa_h ",
+ "SI_superR134a_1p2MPa_s ",
+
+ "SI_superR134a_1p2MPa_T ", "SI_superR134a_1p2MPa_v ", "SI_superR134a_1p2MPa_u ", "SI_superR134a_1p2MPa_h ",
+ "SI_superR134a_1p2MPa_s "
+);
+
+#######################################################
+#######################################################
+### ###
+### below are the lists of the tabulated values ###
+### ###
+#######################################################
+#######################################################
+
+###########################
+### ###
+### saturated water ###
+### ###
+###########################
+
+@SI_water_Tsat = qw(
+ 0.01 5.00 6.97 10.00 13.02 15.00 17.50 20.00 21.08 24.08 25.00 28.96 30.00 32.87 35.00 40.00 40.29 45.00 45.81 50.00 53.97 55.00 60.00 60.06 65.00 69.09 70.00 75.00 75.86 80.00 81.32 85.00 90.00 91.76 95.00 99.61 105.00 105.97 110.00 111.35 115.00 116.04 120.00 120.21 123.97 125.00 127.41 130.00 133.52 135.00 136.27 138.86 140.00 141.30 143.61 145.00 147.90 150.00 151.83 155.00 155.46 158.83 160.00 161.98 165.00 167.75 170.00 170.41 172.94 175.00 177.66 180.00 184.06 185.00 187.96 190.00 191.60 195.00 198.29 200.00 205.00 205.72 210.00 212.38 215.00 218.41 220.00 223.95 225.00 230.00 235.00 240.00 242.56 245.00 250.00 255.00 260.00 263.94 265.00 270.00 275.00 275.59 280.00 285.00 285.83 290.00 295.00 300.00 303.35 305.00 310.00 311.00 315.00 318.08 320.00 324.68 325.00 330.00 330.85 335.00 336.67 340.00 342.16 345.00 347.36 350.00 352.29 355.00 356.99 360.00 361.47 365.00 365.75 369.83 370.00 373.71 373.95
+);
+
+@SI_water_Psat = qw(
+ 0.6117 0.8725 1.0000 1.2281 1.5000 1.7057 2.0000 2.3392 2.5000 3.0000 3.1698 4.0000 4.2469 5.0000 5.6291 7.3851 7.5000 9.5953 10.0000 12.3520 15.0000 15.7630 19.9470 20.0000 25.0430 30.0000 31.2020 38.5970 40.0000 47.4160 50.0000 57.8680 70.1830 75.0000 84.6090 100.0000 120.9000 125.0000 143.3800 150.0000 169.1800 175.0000 198.6700 200.0000 225.0000 232.2300 250.0000 270.2800 300.0000 313.2200 325.0000 350.0000 361.5300 375.0000 400.0000 415.6800 450.0000 476.1600 500.0000 543.4900 550.0000 600.0000 618.2300 650.0000 700.9300 750.0000 792.1800 800.0000 850.0000 892.6000 950.0000 1002.8000 1100.0000 1123.5000 1200.0000 1255.2000 1300.0000 1398.8000 1500.0000 1554.9000 1724.3000 1750.0000 1907.7000 2000.0000 2105.9000 2250.0000 2319.6000 2500.0000 2549.7000 2797.1000 3062.6000 3347.0000 3500.0000 3651.2000 3976.2000 4322.9000 4692.3000 5000.0000 5085.3000 5503.0000 5946.4000 6000.0000 6416.6000 6914.6000 7000.0000 7441.8000 7999.0000 8587.9000 9000.0000 9209.4000 9865.0000 10000.0000 10556.0000 11000.0000 11284.0000 12000.0000 12051.0000 12858.0000 13000.0000 13707.0000 14000.0000 14601.0000 15000.0000 15541.0000 16000.0000 16529.0000 17000.0000 17570.0000 18000.0000 18666.0000 19000.0000 19822.0000 20000.0000 21000.0000 21044.0000 22000.0000 22064.0000
+);
+
+@SI_water_vf = qw(
+ 0.001 0.001 0.001 0.001 0.001001 0.001001 0.001001 0.001002 0.001002 0.001003 0.001003 0.001004 0.001004 0.001005 0.001006 0.001008 0.001008 0.00101 0.00101 0.001012 0.001014 0.001015 0.001017 0.001017 0.00102 0.001022 0.001023 0.001026 0.001026 0.001029 0.00103 0.001032 0.001036 0.001037 0.00104 0.001043 0.001047 0.001048 0.001052 0.001053 0.001056 0.001057 0.00106 0.001061 0.001064 0.001065 0.001067 0.00107 0.001073 0.001075 0.001076 0.001079 0.00108 0.001081 0.001084 0.001085 0.001088 0.001091 0.001093 0.001096 0.001097 0.001101 0.001102 0.001104 0.001108 0.001111 0.001114 0.001115 0.001118 0.001121 0.001124 0.001127 0.001133 0.001134 0.001138 0.001141 0.001144 0.001149 0.001154 0.001157 0.001164 0.001166 0.001173 0.001177 0.001181 0.001187 0.00119 0.001197 0.001199 0.001209 0.001219 0.001229 0.001235 0.00124 0.001252 0.001263 0.001276 0.001286 0.001289 0.001303 0.001317 0.001319 0.001333 0.001349 0.001352 0.001366 0.001384 0.001404 0.001418 0.001425 0.001447 0.001452 0.001472 0.001488 0.001499 0.001526 0.001528 0.00156 0.001566 0.001597 0.00161 0.001638 0.001657 0.001685 0.00171 0.001741 0.00177 0.001808 0.00184 0.001895 0.001926 0.002015 0.002038 0.002207 0.002217 0.002703 0.003106
+);
+
+@SI_water_vfg = qw(
+ 205.999000 147.029000 129.189000 106.319000 87.962999 77.883999 66.988999 57.760998 54.240998 45.652997 43.338997 34.789996 32.877996 28.183995 25.203994 19.513992 19.231992 15.249990 14.668990 12.024988 10.018986 9.562885 7.665983 7.647083 6.192480 5.227678 5.038577 4.128074 3.992274 3.404271 3.239270 2.825068 2.358264 2.216163 1.979760 1.693057 1.417553 1.373952 1.208348 1.158347 1.034944 1.002643 0.890270 0.884719 0.792226 0.769055 0.717663 0.667010 0.604747 0.580715 0.560914 0.523141 0.507420 0.490249 0.461336 0.444915 0.412832 0.391389 0.373737 0.345384 0.341513 0.314499 0.305698 0.291496 0.271332 0.254409 0.241486 0.239235 0.225782 0.215469 0.202986 0.192713 0.176317 0.172766 0.162122 0.155219 0.150046 0.139741 0.130556 0.126053 0.113916 0.112274 0.103117 0.098410 0.093499 0.087530 0.084904 0.078755 0.077206 0.070296 0.064081 0.058478 0.055826 0.053416 0.048833 0.044678 0.040899 0.038162 0.037459 0.034319 0.031450 0.031130 0.028820 0.026407 0.026026 0.024188 0.022144 0.020255 0.019071 0.018507 0.016886 0.016576 0.015377 0.014500 0.013971 0.012738 0.012655 0.011419 0.011215 0.010251 0.009877 0.009145 0.008684 0.008087 0.007602 0.007065 0.006604 0.006064 0.005664 0.005055 0.004751 0.003994 0.003824 0.002787 0.002736 0.000941 0.000000
+);
+
+@SI_water_vg = qw(
+ 206 147.03 129.19 106.32 87.964 77.885 66.99 57.762 54.242 45.654 43.34 34.791 32.879 28.185 25.205 19.515 19.233 15.251 14.67 12.026 10.02 9.5639 7.667 7.6481 6.1935 5.2287 5.0396 4.1291 3.9933 3.4053 3.2403 2.8261 2.3593 2.2172 1.9808 1.6941 1.4186 1.375 1.2094 1.1594 1.036 1.0037 0.89133 0.88578 0.79329 0.77012 0.71873 0.66808 0.60582 0.58179 0.56199 0.52422 0.5085 0.49133 0.46242 0.446 0.41392 0.39248 0.37483 0.34648 0.34261 0.3156 0.3068 0.2926 0.27244 0.25552 0.2426 0.24035 0.2269 0.21659 0.20411 0.19384 0.17745 0.1739 0.16326 0.15636 0.15119 0.14089 0.13171 0.12721 0.11508 0.11344 0.10429 0.099587 0.09468 0.088717 0.086094 0.079952 0.078405 0.071505 0.0653 0.059707 0.057061 0.054656 0.050085 0.045941 0.042175 0.039448 0.038748 0.035622 0.032767 0.032449 0.030153 0.027756 0.027378 0.025554 0.023528 0.021659 0.020489 0.019932 0.018333 0.018028 0.016849 0.015988 0.01547 0.014264 0.014183 0.012979 0.012781 0.011848 0.011487 0.010783 0.010341 0.009772 0.009312 0.008806 0.008374 0.007872 0.007504 0.00695 0.006677 0.006009 0.005862 0.004994 0.004953 0.003644 0.003106
+);
+
+@SI_water_uf = qw(
+ 0 21.019 29.302 42.02 54.686 62.98 73.431 83.913 88.422 100.98 104.83 121.39 125.73 137.75 146.63 167.53 168.74 188.43 191.79 209.33 225.93 230.24 251.16 251.4 272.09 289.24 293.04 313.99 317.58 334.97 340.49 355.96 376.97 384.36 398 417.4 440.15 444.23 461.27 466.97 482.42 486.82 503.6 504.5 520.47 524.83 535.08 546.1 561.11 567.41 572.84 583.89 588.77 594.32 604.22 610.19 622.65 631.66 639.54 653.19 655.16 669.72 674.79 683.37 696.46 708.4 718.2 719.97 731 740.02 751.67 761.92 779.78 783.91 796.96 806 813.1 828.18 842.82 850.46 872.86 876.12 895.38 906.12 918.02 933.54 940.79 958.87 963.7 986.76 1010 1033.4 1045.4 1056.9 1080.7 1104.7 1128.8 1148.1 1153.3 1177.9 1202.9 1205.8 1228.2 1253.7 1258 1279.7 1306 1332.7 1350.9 1360 1387.7 1393.3 1416.1 1433.9 1445.1 1473 1475 1505.7 1511 1537.5 1548.4 1570.7 1585.5 1605.5 1622.6 1642.4 1660.2 1682.2 1699.1 1726.2 1740.3 1777.2 1785.8 1841.6 1844.5 1951.7 2015.7
+);
+
+@SI_water_ufg = qw(
+ 2374.9 2360.8 2355.2 2346.6 2338.1 2332.5 2325.5 2318.4 2315.4 2306.9 2304.3 2293.1 2290.2 2282.1 2276 2261.9 2261.1 2247.7 2245.4 2233.4 2222.1 2219.1 2204.7 2204.6 2190.3 2178.5 2175.8 2161.3 2158.8 2146.6 2142.7 2131.9 2117 2111.8 2102 2088.2 2071.8 2068.8 2056.4 2052.3 2040.9 2037.7 2025.3 2024.6 2012.7 2009.5 2001.8 1993.4 1982.1 1977.3 1973.1 1964.6 1960.9 1956.6 1948.9 1944.2 1934.5 1927.4 1921.2 1910.3 1908.8 1897.1 1893 1886.1 1875.4 1865.6 1857.5 1856.1 1846.9 1839.4 1829.6 1820.9 1805.7 1802.1 1790.9 1783 1776.8 1763.6 1750.6 1743.7 1723.5 1720.6 1702.9 1693 1681.9 1667.3 1660.5 1643.2 1638.6 1616.1 1593.2 1569.8 1557.6 1545.7 1521.1 1495.8 1469.9 1448.9 1443.2 1415.7 1387.4 1384.1 1358.2 1328.1 1323 1296.9 1264.5 1230.9 1207.6 1195.9 1159.3 1151.8 1121.1 1096.6 1080.9 1041.3 1038.5 993.5 985.5 945.5 928.7 893.8 870.3 837.7 809.4 775.9 745.1 706.4 675.9 625.7 598.9 526.4 509 391.9 385.6 140.8 0
+);
+
+@SI_water_ug = qw(
+ 2374.9 2381.8 2384.5 2388.7 2392.8 2395.5 2398.9 2402.3 2403.8 2407.9 2409.1 2414.5 2415.9 2419.8 2422.7 2429.4 2429.8 2436.1 2437.2 2442.7 2448 2449.3 2455.9 2456 2462.4 2467.7 2468.9 2475.3 2476.3 2481.6 2483.2 2487.8 2494 2496.1 2500.1 2505.6 2511.9 2513 2517.7 2519.2 2523.3 2524.5 2528.9 2529.1 2533.2 2534.3 2536.8 2539.5 2543.2 2544.7 2545.9 2548.5 2549.6 2550.9 2553.1 2554.4 2557.1 2559.1 2560.7 2563.5 2563.9 2566.8 2567.8 2569.4 2571.9 2574 2575.7 2576 2577.9 2579.4 2581.3 2582.8 2585.5 2586 2587.8 2589 2589.9 2591.7 2593.4 2594.2 2596.4 2596.7 2598.3 2599.1 2599.9 2600.9 2601.3 2602.1 2602.3 2602.9 2603.2 2603.1 2603 2602.7 2601.8 2600.5 2598.7 2597 2596.5 2593.7 2590.3 2589.9 2586.4 2581.8 2581 2576.5 2570.5 2563.6 2558.5 2555.8 2547.1 2545.2 2537.2 2530.4 2526 2514.3 2513.4 2499.2 2496.6 2483 2477.1 2464.5 2455.7 2443.2 2432 2418.3 2405.4 2388.6 2375 2351.9 2339.2 2303.6 2294.8 2233.5 2230.1 2092.4 2015.7
+);
+
+@SI_water_hf = qw(
+ 0.001 21.02 29.303 42.022 54.688 62.982 73.433 83.915 88.424 100.98 104.83 121.39 125.74 137.75 146.64 167.53 168.75 188.44 191.81 209.34 225.94 230.26 251.18 251.42 272.12 289.27 293.07 314.03 317.62 335.02 340.54 356.02 377.04 384.44 398.09 417.51 440.28 444.36 461.42 467.13 482.59 487.01 503.81 504.71 520.71 525.07 535.35 546.38 561.43 567.75 573.19 584.26 589.16 594.73 604.66 610.64 623.14 632.18 640.09 653.79 655.77 670.38 675.47 684.08 697.24 709.24 719.08 720.87 731.95 741.02 752.74 763.05 781.03 785.19 798.33 807.43 814.59 829.78 844.55 852.26 874.87 878.16 897.61 908.47 920.5 936.21 943.55 961.87 966.76 990.14 1013.7 1037.5 1049.7 1061.5 1085.7 1110.1 1134.8 1154.5 1159.8 1185.1 1210.7 1213.8 1236.7 1263.1 1267.5 1289.8 1317.1 1344.8 1363.7 1373.1 1402 1407.8 1431.6 1450.2 1462 1491.3 1493.4 1525.8 1531.4 1559.4 1571 1594.6 1610.3 1631.7 1649.9 1671.2 1690.3 1714 1732.2 1761.5 1776.8 1817.2 1826.6 1888 1891.2 2011.1 2084.3
+);
+
+@SI_water_hfg = qw(
+ 2500.9 2489.1 2484.4 2477.2 2470.1 2465.4 2459.5 2453.5 2451 2443.9 2441.7 2432.3 2429.8 2423 2417.9 2406 2405.3 2394 2392.1 2382 2372.3 2369.8 2357.7 2357.5 2345.4 2335.3 2333 2320.6 2318.4 2308 2304.7 2295.3 2282.5 2278 2269.6 2257.5 2243.1 2240.6 2229.7 2226 2216 2213.1 2202.1 2201.6 2191 2188.1 2181.2 2173.7 2163.5 2159.1 2155.4 2147.7 2144.3 2140.4 2133.4 2129.2 2120.3 2113.8 2108 2098 2096.6 2085.8 2082 2075.5 2065.6 2056.4 2048.8 2047.5 2038.8 2031.7 2022.4 2014.2 1999.6 1996.2 1985.4 1977.9 1971.9 1959 1946.4 1939.8 1920 1917.1 1899.7 1889.8 1878.8 1864.3 1857.4 1840.1 1835.4 1812.8 1789.5 1765.5 1753 1740.8 1715.3 1689 1661.8 1639.7 1633.7 1604.6 1574.5 1570.9 1543.2 1510.7 1505.2 1476.9 1441.6 1404.8 1379.3 1366.3 1325.9 1317.6 1283.4 1256.1 1238.5 1194.1 1191 1140.3 1131.3 1086 1067 1027.4 1000.5 963.4 931.1 892.7 857.4 812.9 777.8 720.1 689.2 605.5 585.5 450.4 443.1 161.5 0
+);
+
+@SI_water_hg = qw(
+ 2500.9 2510.1 2513.7 2519.2 2524.7 2528.3 2532.9 2537.4 2539.4 2544.8 2546.5 2553.7 2555.6 2560.7 2564.6 2573.5 2574 2582.4 2583.9 2591.3 2598.3 2600.1 2608.8 2608.9 2617.5 2624.6 2626.1 2634.6 2636.1 2643 2645.2 2651.4 2659.6 2662.4 2667.6 2675 2683.4 2684.9 2691.1 2693.1 2698.6 2700.2 2706 2706.3 2711.7 2713.1 2716.5 2720.1 2724.9 2726.9 2728.6 2732 2733.5 2735.1 2738.1 2739.8 2743.4 2745.9 2748.1 2751.8 2752.4 2756.2 2757.5 2759.6 2762.8 2765.7 2767.9 2768.3 2770.8 2772.7 2775.2 2777.2 2780.7 2781.4 2783.8 2785.3 2786.5 2788.8 2791 2792 2794.8 2795.2 2797.3 2798.3 2799.3 2800.5 2801 2801.9 2802.2 2802.9 2803.2 2803 2802.7 2802.2 2801 2799.1 2796.6 2794.2 2793.5 2789.7 2785.2 2784.6 2779.9 2773.7 2772.6 2766.7 2758.7 2749.6 2742.9 2739.4 2727.9 2725.5 2715 2706.3 2700.6 2685.4 2684.3 2666 2662.7 2645.4 2637.9 2622 2610.8 2595.1 2581 2563.9 2547.7 2526.9 2510 2481.6 2466 2422.7 2412.1 2338.4 2334.3 2172.6 2084.3
+);
+
+@SI_water_sf = qw(
+ 0 0.0763 0.1059 0.1511 0.1956 0.2245 0.2606 0.2965 0.3118 0.3543 0.3672 0.4224 0.4368 0.4762 0.5051 0.5724 0.5763 0.6386 0.6492 0.7038 0.7549 0.768 0.8313 0.832 0.8937 0.9441 0.9551 1.0158 1.0261 1.0756 1.0912 1.1346 1.1929 1.2132 1.2504 1.3028 1.3634 1.3741 1.4188 1.4337 1.4737 1.485 1.5279 1.5302 1.5706 1.5816 1.6072 1.6346 1.6717 1.6872 1.7005 1.7274 1.7392 1.7526 1.7765 1.7908 1.8205 1.8418 1.8604 1.8924 1.897 1.9308 1.9426 1.9623 1.9923 2.0195 2.0417 2.0457 2.0705 2.0906 2.1166 2.1392 2.1785 2.1875 2.2159 2.2355 2.2508 2.2831 2.3143 2.3305 2.3776 2.3844 2.4245 2.4467 2.4712 2.5029 2.5176 2.5542 2.5639 2.61 2.656 2.7018 2.7253 2.7476 2.7933 2.839 2.8847 2.9207 2.9304 2.9762 3.0221 3.0275 3.0681 3.1144 3.122 3.1608 3.2076 3.2548 3.2866 3.3024 3.3506 3.3603 3.3994 3.4299 3.4491 3.4964 3.4998 3.5516 3.5606 3.605 3.6232 3.6602 3.6848 3.7179 3.7461 3.7788 3.8082 3.8442 3.872 3.9165 3.9396 4.0004 4.0146 4.1071 4.1119 4.2942 4.407
+);
+
+@SI_water_sfg = qw(
+ 9.1556 8.9487 8.869 8.7488 8.6314 8.5559 8.4621 8.3696 8.3302 8.2222 8.1895 8.051 8.0152 7.9176 7.8466 7.6832 7.6738 7.5247 7.4996 7.371 7.2522 7.2218 7.0769 7.0752 6.936 6.8234 6.7989 6.6655 6.643 6.5355 6.5019 6.4089 6.2853 6.2426 6.1647 6.0562 5.9319 5.91 5.8193 5.7894 5.7092 5.6865 5.6013 5.5968 5.5171 5.4956 5.4453 5.3919 5.32 5.2901 5.2645 5.2128 5.1901 5.1645 5.1191 5.0919 5.0356 4.9953 4.9603 4.9002 4.8916 4.8285 4.8066 4.7699 4.7143 4.6642 4.6233 4.616 4.5705 4.5335 4.4862 4.4448 4.3735 4.3572 4.3058 4.2705 4.2428 4.1847 4.1287 4.0997 4.0154 4.0033 3.9318 3.8923 3.8489 3.7926 3.7664 3.7016 3.6844 3.6028 3.5216 3.4405 3.3991 3.3596 3.2788 3.1979 3.1169 3.053 3.0358 2.9542 2.8723 2.8627 2.7898 2.7066 2.6927 2.6225 2.5374 2.4511 2.3925 2.3633 2.2737 2.2556 2.1821 2.1245 2.0881 1.9975 1.9911 1.8906 1.873 1.7857 1.7497 1.6756 1.6261 1.5585 1.5005 1.4326 1.3709 1.2942 1.2343 1.1373 1.086 0.9489 0.9164 0.7005 0.689 0.2496 0
+);
+
+@SI_water_sg = qw(
+ 9.1556 9.0249 8.9749 8.8999 8.827 8.7803 8.7227 8.6661 8.6421 8.5765 8.5567 8.4734 8.452 8.3938 8.3517 8.2556 8.2501 8.1633 8.1488 8.0748 8.0071 7.9898 7.9082 7.9073 7.8296 7.7675 7.754 7.6812 7.6691 7.6111 7.5931 7.5435 7.4782 7.4558 7.4151 7.3589 7.2952 7.2841 7.2382 7.2231 7.1829 7.1716 7.1292 7.127 7.0877 7.0771 7.0525 7.0265 6.9917 6.9773 6.965 6.9402 6.9294 6.9171 6.8955 6.8827 6.8561 6.8371 6.8207 6.7927 6.7886 6.7593 6.7492 6.7322 6.7067 6.6837 6.665 6.6616 6.6409 6.6242 6.6027 6.5841 6.552 6.5447 6.5217 6.5059 6.4936 6.4678 6.443 6.4302 6.393 6.3877 6.3563 6.339 6.32 6.2954 6.284 6.2558 6.2483 6.2128 6.1775 6.1424 6.1244 6.1072 6.0721 6.0369 6.0017 5.9737 5.9662 5.9305 5.8944 5.8902 5.8579 5.821 5.8148 5.7834 5.745 5.7059 5.6791 5.6657 5.6243 5.6159 5.5816 5.5544 5.5372 5.4939 5.4908 5.4422 5.4336 5.3907 5.3728 5.3358 5.3108 5.2765 5.2466 5.2114 5.1791 5.1384 5.1064 5.0537 5.0256 4.9493 4.931 4.8076 4.8009 4.5439 4.407
+);
+
+###############
+### ###
+### air ###
+### ###
+###############
+
+@SI_air_T = qw(
+ 200 210 220 230 240 250 260 270 280 285 290 295 298 300 305 310 315 320 325 330 340 350 360 370 380 390 400 410 420 430 440 450 460 470 480 490 500 510 520 530 540 550 560 570 580 590 600 610 620 630 640 650 660 670 680 690 700 710 720 730 740 750 760 780 800 820 840 860 880 900 920 940 960 980 1000 1020 1040 1060 1080 1100 1120 1140 1160 1180 1200 1220 1240 1260 1280 1300 1320 1340 1360 1380 1400 1420 1440 1460 1480 1500 1520 1540 1560 1580 1600 1620 1640 1660 1680 1700 1750 1800 1850 1900 1950 2000 2050 2100 2150 2200 2250
+);
+
+@SI_air_h = qw(
+ 199.97 209.97 219.97 230.02 240.02 250.05 260.09 270.11 280.13 285.14 290.16 295.17 298.18 300.19 305.22 310.24 315.27 320.29 325.31 330.34 340.42 350.49 360.58 370.67 380.77 390.88 400.98 411.12 421.26 431.43 441.61 451.8 462.02 472.24 482.49 492.74 503.02 513.32 523.63 533.98 544.35 555.74 565.17 575.59 586.04 596.52 607.02 617.53 628.07 638.63 649.22 659.84 670.47 681.14 691.82 702.52 713.27 724.04 734.82 745.62 756.44 767.29 778.18 800.03 821.95 843.98 866.08 888.27 910.56 932.93 955.38 977.92 1000.55 1023.25 1046.04 1068.89 1091.85 1114.86 1137.89 1161.07 1184.28 1207.57 1230.92 1254.34 1277.79 1301.31 1324.93 1348.55 1372.24 1395.97 1419.76 1443.6 1467.49 1491.44 1515.42 1539.44 1563.51 1587.63 1611.79 1635.97 1660.23 1684.51 1708.82 1733.17 1757.57 1782 1806.46 1830.96 1855.5 1880.1 1941.6 2003.3 2065.3 2127.4 2189.7 2252.1 2314.6 2377.7 2440.3 2503.2 2566.4
+);
+
+@SI_air_Pr = qw(
+ 0.3363 0.3987 0.469 0.5477 0.6355 0.7329 0.8405 0.959 1.0889 1.1584 1.2311 1.3068 1.3543 1.386 1.4686 1.5546 1.6442 1.7375 1.8345 1.9352 2.149 2.379 2.626 2.892 3.176 3.481 3.806 4.153 4.522 4.915 5.332 5.775 6.245 6.742 7.268 7.824 8.411 9.031 9.684 10.37 11.1 11.86 12.66 13.5 14.38 15.31 16.28 17.3 18.36 19.84 20.64 21.86 23.13 24.46 25.85 27.29 28.8 30.38 32.02 33.72 35.5 37.35 39.27 43.35 47.75 52.59 57.6 63.09 68.98 75.29 82.05 89.28 97 105.2 114 123.4 133.3 143.9 155.2 167.1 179.7 193.1 207.2 222.2 238 254.7 272.3 290.8 310.4 330.9 352.5 375.3 399.1 424.2 450.5 478 506.9 537.1 568.8 601.9 636.5 672.8 710.5 750 791.2 834.1 878.9 925.6 974.2 1025 1161 1310 1475 1655 1852 2068 2303 2559 2837 3138 3464
+);
+
+@SI_air_u = qw(
+ 142.56 149.69 156.82 164 171.13 178.28 185.45 192.6 199.75 203.33 206.91 210.49 212.64 214.07 217.67 221.25 224.85 228.42 232.02 235.61 242.82 250.02 257.24 264.46 271.69 278.93 286.16 293.43 300.69 307.99 315.3 322.62 329.97 337.32 344.7 352.08 359.49 366.92 374.36 381.84 389.34 396.86 404.42 411.97 419.55 427.15 434.78 442.42 450.09 457.78 465.5 473.25 481.01 488.81 496.62 504.45 512.33 520.23 528.14 536.07 544.02 551.99 560.01 576.12 592.3 608.59 624.95 641.4 657.95 674.58 691.28 708.08 725.02 741.98 758.94 776.1 793.36 810.62 827.88 845.33 862.79 880.35 897.91 915.57 933.33 951.09 968.95 986.9 1004.76 1022.82 1040.88 1058.94 1077.1 1095.26 1113.52 1131.77 1150.13 1168.49 1186.95 1205.41 1223.87 1242.43 1260.99 1279.65 1298.3 1316.96 1335.72 1354.48 1373.24 1392.7 1439.8 1487.2 1534.9 1582.6 1630.6 1678.7 1726.8 1775.3 1823.8 1872.4 1921.3
+);
+
+@SI_air_vr = qw(
+ 1707 1512 1346 1205 1084 979 887.8 808 738 706.1 676.1 647.9 631.9 621.2 596 572.3 549.8 528.6 508.4 489.4 454.1 422.2 393.4 367.2 343.4 321.5 301.6 283.3 266.6 251.1 236.8 223.6 211.4 200.1 189.5 179.7 170.6 162.1 154.1 146.7 139.7 133.1 127 121.2 115.7 110.6 105.8 101.2 96.92 92.84 88.99 85.34 81.89 78.61 75.5 72.56 69.76 67.07 64.53 62.13 59.82 57.63 55.54 51.64 48.08 44.84 41.85 39.12 36.61 34.31 32.18 30.22 28.4 26.73 25.17 23.72 23.29 21.14 19.98 18.896 17.886 16.946 16.064 15.241 14.47 13.747 13.069 12.435 11.835 11.275 10.747 10.247 9.78 9.337 8.919 8.526 8.153 7.801 7.468 7.152 6.854 6.569 6.301 6.046 5.804 5.574 5.355 5.147 4.949 4.761 4.328 3.994 3.601 3.295 3.022 2.776 2.555 2.356 2.175 2.012 1.864
+);
+
+@SI_air_so = qw(
+ 1.29559 1.34444 1.39105 1.43557 1.47824 1.51917 1.55848 1.59634 1.63279 1.65055 1.66802 1.68515 1.69528 1.70203 1.71865 1.73498 1.75106 1.7669 1.78249 1.79783 1.8279 1.85708 1.88543 1.91313 1.94001 1.96633 1.99194 2.01699 2.04142 2.06533 2.0887 2.11161 2.13407 2.15604 2.1776 2.19876 2.21952 2.23993 2.25997 2.27967 2.29906 2.31809 2.33685 2.35531 2.37348 2.3914 2.40902 2.42644 2.44356 2.46048 2.47716 2.49364 2.50985 2.52589 2.54175 2.55731 2.57277 2.5881 2.60319 2.61803 2.6328 2.64737 2.66176 2.69013 2.71787 2.74504 2.7717 2.79783 2.82344 2.84856 2.87324 2.89748 2.92128 2.94468 2.9677 2.99034 3.0126 3.03449 3.05608 3.07732 3.09825 3.11883 3.13916 3.15916 3.17888 3.19834 3.21751 3.23638 3.2551 3.27345 3.2916 3.30959 3.32724 3.34474 3.362 3.37901 3.39586 3.41247 3.42892 3.44516 3.4612 3.47712 3.49276 3.50829 3.52364 3.53879 3.55381 3.56867 3.58335 3.5979 3.6336 3.6684 3.7023 3.7354 3.7677 3.7994 3.8303 3.8605 3.8901 3.9191 3.9474
+);
+
+#############################
+### ###
+### superheated water ###
+### ###
+#############################
+
+#### ####
+#### 0.1 MPa ####
+#### ####
+
+@SI_superWater_0p1MPa_T = qw(
+ 99.61 100 150 200 250 300 400 500 600 700 800 900 1000 1100 1200 1300
+);
+
+@SI_superWater_0p1MPa_v = qw(
+ 1.6941 1.6959 1.9367 2.1724 2.4062 2.6389 3.1027 3.5655 4.0279 4.49 4.9519 5.4137 5.8755 6.3372 6.7988 7.2605
+);
+
+@SI_superWater_0p1MPa_u = qw(
+ 2505.6 2506.2 2582.9 2658.2 2733.9 2810.7 2968.3 3132.2 3302.8 3480.4 3665 3856.7 4055 4259.8 4470.7 4687.2
+);
+
+@SI_superWater_0p1MPa_h = qw(
+ 2675 2675.8 2776.6 2875.5 2974.5 3074.5 3278.6 3488.7 3705.6 3929.4 4160.2 4398 4642.6 4893.6 5150.6 5413.3
+);
+
+@SI_superWater_0p1MPa_s = qw(
+ 7.3589 7.3611 7.6148 7.8356 8.0346 8.2172 8.5452 8.8362 9.0999 9.3424 9.5682 9.78 9.98 10.1698 10.3504 10.5229
+);
+
+#### ####
+#### 1.2 MPa ####
+#### ####
+
+@SI_superWater_1p2MPa_T = qw(
+ 179.88 200 250 300 350 400 500 600 700 800 900 1000 1100 1200 1300
+);
+
+@SI_superWater_1p2MPa_v = qw(
+ 0.16326 0.16934 0.19241 0.21386 0.23455 0.25482 0.29464 0.33395 0.37297 0.41184 0.45059 0.48928 0.52792 0.56652 0.60509
+);
+
+@SI_superWater_1p2MPa_u = qw(
+ 2587.8 2612.9 2704.7 2789.7 2872.7 2955.5 3123.4 3296.3 3475.3 3661 3853.3 4052.2 4257.5 4468.7 4685.5
+);
+
+@SI_superWater_1p2MPa_h = qw(
+ 2783.8 2816.1 2935.6 3046.3 3154.2 3261.3 3477 3697 3922.9 4155.2 4394 4639.4 4891 5148.5 5411.6
+);
+
+@SI_superWater_1p2MPa_s = qw(
+ 6.5217 6.5909 6.8313 7.0335 7.2139 7.3793 7.6779 7.9456 8.1904 8.4176 8.6303 8.831 9.0212 9.2022 9.375
+);
+
+#### ####
+#### 1.4 MPa ####
+#### ####
+
+@SI_superWater_1p4MPa_T = qw(
+ 195.04 200 250 300 350 400 500 600 700 800 900 1000 1100 1200 1300
+);
+
+@SI_superWater_1p4MPa_v = qw(
+ 0.14078 0.14303 0.16356 0.18233 0.20029 0.21782 0.25216 0.28597 0.31951 0.35288 0.38614 0.41933 0.45247 0.48558 0.51866
+);
+
+@SI_superWater_1p4MPa_u = qw(
+ 2591.8 2602.7 2698.9 2785.7 2869.7 2953.1 3121.8 3295.1 3474.4 3660.3 3852.7 4051.7 4257 4468.3 4685.1
+);
+
+@SI_superWater_1p4MPa_h = qw(
+ 2788.9 2803 2927.9 3040.9 3150.1 3258.1 3474.8 3695.5 3921.7 4154.3 4393.3 4638.8 4890.5 5148.1 5411.3
+);
+
+@SI_superWater_1p4MPa_s = qw(
+ 6.4675 6.4975 6.7488 6.9553 7.1379 7.3046 7.6047 7.873 8.1183 8.3458 8.5587 8.7595 8.9497 9.1308 9.3036
+);
+
+#### ####
+#### 1.6 MPa ####
+#### ####
+
+@SI_superWater_1p6MPa_T = qw(
+ 201.37 225 250 300 350 400 500 600 700 800 900 1000 1100 1200 1300
+);
+
+@SI_superWater_1p6MPa_v = qw(
+ 0.12374 0.13293 0.1419 0.15866 0.17459 0.19007 0.22029 0.24999 0.27941 0.30865 0.3378 0.36687 0.39589 0.42488 0.45383
+);
+
+@SI_superWater_1p6MPa_u = qw(
+ 2594.8 2645.1 2692.9 2781.6 2866.6 2950.8 3120.1 3293.9 3473.5 3659.5 3852.1 4051.2 4256.6 4467.9 4684.8
+);
+
+@SI_superWater_1p6MPa_h = qw(
+ 2792.8 2857.8 2919.9 3035.4 3146 3254.9 3472.6 3693.9 3920.5 4153.4 4392.6 4638.2 4890 5147.7 5410.9
+);
+
+@SI_superWater_1p6MPa_s = qw(
+ 6.42 6.5537 6.6753 6.8864 7.0713 7.2394 7.541 7.8101 8.0558 8.2834 8.4965 8.6974 8.8878 9.0689 9.2418
+);
+
+#### ####
+#### 1.8 MPa ####
+#### ####
+
+@SI_superWater_1p8MPa_T = qw(
+ 207.11 225 250 300 350 400 500 600 700 800 900 1000 1100 1200 1300
+);
+
+@SI_superWater_1p8MPa_v = qw(
+ 0.11037 0.11678 0.12502 0.14025 0.1546 0.16849 0.19551 0.222 0.24822 0.27426 0.3002 0.32606 0.35188 0.37766 0.40341
+);
+
+@SI_superWater_1p8MPa_u = qw(
+ 2597.3 2637 2686.7 2777.4 2863.6 2948.3 3118.5 3292.7 3472.6 3658.8 3851.5 4050.7 4256.2 4467.6 4684.5
+);
+
+@SI_superWater_1p8MPa_h = qw(
+ 2795.9 2847.2 2911.7 3029.9 3141.9 3251.6 3470.4 3692.3 3919.4 4152.4 4391.9 4637.6 4889.6 5147.3 5410.6
+);
+
+@SI_superWater_1p8MPa_s = qw(
+ 6.3775 6.4825 6.6088 6.8246 7.012 7.1814 7.4845 7.7543 8.0005 8.2284 8.4417 8.6427 8.8331 9.0143 9.1872
+);
+
+#### ####
+#### 10 MPa ####
+#### ####
+
+@SI_superWater_10MPa_T = qw(
+ 311 325 350 400 450 500 550 600 650 700 800 900 1000 1100 1200 1300
+);
+
+@SI_superWater_10MPa_v = qw(
+ .018028 0.019877 0.02244 0.026436 0.029782 0.032811 0.035655 0.038378 0.041018 0.043597 0.048629 0.053547 0.058391 0.063183 0.067938 0.072667
+);
+
+@SI_superWater_10MPa_u = qw(
+ 2545.2 2611.6 2699.6 2833.1 2944.5 3047 3145.4 3242 3338 3434 3628.2 3826.5 4029.9 4238.5 4452.4 4671.3
+);
+
+@SI_superWater_10MPa_h = qw(
+ 2725.5 2810.3 2924 3097.5 3242.4 3375.1 3502 3625.8 3748.1 3870 4114.5 4362 4613.8 4870.3 5131.7 5398
+);
+
+@SI_superWater_10MPa_s = qw(
+ 5.6159 5.7596 5.946 6.2141 6.4219 6.5995 6.7585 6.9045 7.0408 7.1693 7.4085 7.629 7.8349 8.0289 8.2126 8.3874
+);
+
+############################
+### ###
+### saturated R-134a ###
+### ###
+############################
+
+@SI_R134a_Tsat = qw(
+ -40.00 -38.00 -36.95 -36.00 -34.00 -33.87 -32.00 -31.13 -30.00 -28.65 -28.00 -26.37 -26.00 -24.00 -22.32 -22.00 -20.00 -18.77 -18.00 -16.00 -15.60 -14.00 -12.73 -12.00 -10.09 -10.00 -8.00 -6.00 -5.38 -4.00 -2.00 -1.25 0.00 2.00 2.46 4.00 5.82 6.00 8.00 8.91 10.00 12.00 12.46 14.00 15.71 16.00 18.00 18.73 20.00 21.55 22.00 24.00 24.20 26.00 26.69 28.00 29.06 30.00 31.31 32.00 33.45 34.00 35.51 36.00 37.48 38.00 39.37 40.00 42.00 44.00 46.00 46.29 48.00 52.00 52.40 56.00 57.88 60.00 62.87 65.00 67.45 70.00 75.00 77.54 80.00 85.00 86.16 90.00 95.00 100.00
+);
+
+@SI_R134a_Psat = qw(
+ 51.250 56.860 60.00 62.950 69.560 70.00 76.710 80.00 84.430 90.00 92.760 100.00 101.730 111.370 120.00 121.720 132.820 140.00 144.690 157.380 160.00 170.930 180.00 185.370 200.00 200.740 217.080 234.440 240.00 252.850 272.360 280.00 293.010 314.840 320.00 337.900 360.00 362.230 387.880 400.00 414.890 443.310 450.00 473.190 500.00 504.580 537.520 550.00 572.070 600.00 608.270 646.180 650.00 685.840 700.00 727.310 750.00 770.640 800.00 815.890 850.00 863.110 900.00 912.350 950.00 963.680 1000.00 1017.100 1072.800 1130.700 1191.000 1200.00 1253.600 1386.200 1400.00 1529.100 1600.00 1682.800 1800.00 1891.000 2000.00 2118.200 2365.800 2500.00 2635.300 2928.200 3000.00 3246.900 3594.100 3975.100
+);
+
+@SI_R134a_vf = qw(
+ 0.0007054 0.0007083 0.0007098 0.0007112 0.0007142 0.0007144 0.0007172 0.0007185 0.0007203 0.0007223 0.0007234 0.0007259 0.0007265 0.0007297 0.0007324 0.0007329 0.0007362 0.0007383 0.0007396 0.000743 0.0007437 0.0007464 0.0007487 0.0007499 0.0007533 0.0007535 0.0007571 0.0007608 0.000762 0.0007646 0.0007684 0.0007699 0.0007723 0.0007763 0.0007772 0.0007804 0.0007841 0.0007845 0.0007887 0.0007907 0.000793 0.0007975 0.0007985 0.000802 0.0008059 0.0008066 0.0008113 0.000813 0.0008161 0.0008199 0.000821 0.0008261 0.0008266 0.0008313 0.0008331 0.0008366 0.0008395 0.0008421 0.0008458 0.0008478 0.000852 0.0008536 0.000858 0.0008595 0.0008641 0.0008657 0.00087 0.000872 0.0008786 0.0008854 0.0008924 0.0008934 0.0008996 0.000915 0.0009166 0.0009317 0.00094 0.0009498 0.0009639 0.000975 0.0009886 0.0010037 0.0010372 0.0010566 0.0010772 0.001127 0.0011406 0.0011932 0.0012933 0.0015269
+);
+
+@SI_R134a_vfg = qw(
+ 0.360105 0.326612 0.310500 0.296799 0.270186 0.268576 0.246393 0.236812 0.225080 0.211908 0.205937 0.191814 0.188734 0.173220 0.161388 0.159217 0.146554 0.139402 0.135090 0.124677 0.122736 0.115224 0.109661 0.106610 0.099114 0.098763 0.091595 0.085041 0.083135 0.079039 0.073536 0.071582 0.068483 0.063836 0.062827 0.059558 0.055954 0.055614 0.051973 0.050410 0.048610 0.045498 0.044821 0.042615 0.040312 0.039941 0.037460 0.036595 0.035153 0.033475 0.033007 0.031008 0.030819 0.029145 0.028528 0.027405 0.026532 0.025780 0.024775 0.024260 0.023217 0.022837 0.021825 0.021505 0.020574 0.020253 0.019443 0.019080 0.017976 0.016939 0.015961 0.015822 0.015039 0.013350 0.013190 0.011839 0.011183 0.010484 0.009595 0.008975 0.008299 0.007638 0.006443 0.005879 0.005359 0.004359 0.004134 0.003406 0.002433 0.001103
+);
+
+@SI_R134a_vg = qw(
+ 0.36081 0.32732 0.31121 0.29751 0.2709 0.26929 0.24711 0.23753 0.2258 0.21263 0.20666 0.19254 0.18946 0.17395 0.16212 0.15995 0.14729 0.14014 0.13583 0.12542 0.12348 0.11597 0.11041 0.10736 0.099867 0.099516 0.092352 0.085802 0.083897 0.079804 0.074304 0.072352 0.069255 0.064612 0.063604 0.060338 0.056738 0.056398 0.052762 0.051201 0.049403 0.046295 0.045619 0.043417 0.041118 0.040748 0.038271 0.037408 0.035969 0.034295 0.033828 0.031834 0.031646 0.029976 0.029361 0.028242 0.027371 0.026622 0.025621 0.025108 0.024069 0.023691 0.022683 0.022364 0.021438 0.021119 0.020313 0.019952 0.018855 0.017824 0.016853 0.016715 0.015939 0.014265 0.014107 0.012771 0.012123 0.011434 0.010559 0.00995 0.009288 0.008642 0.00748 0.006936 0.006436 0.005486 0.005275 0.004599 0.003726 0.00263
+);
+
+@SI_R134a_uf = qw(
+ -0.036 2.475 3.798 4.992 7.517 7.68 10.05 11.15 12.59 14.31 15.13 17.21 17.69 20.25 22.4 22.82 25.39 26.98 27.98 30.57 31.09 33.17 34.83 35.78 38.28 38.4 41.03 43.66 44.48 46.31 48.96 49.97 51.63 54.3 54.92 56.99 59.44 59.68 62.39 63.62 65.1 67.83 68.45 70.57 72.93 73.32 76.08 77.1 78.86 81.02 81.64 84.44 84.72 87.26 88.24 90.09 91.59 92.93 94.79 95.79 97.87 98.66 100.83 101.55 103.69 104.45 106.45 107.38 110.32 113.28 116.26 116.7 119.26 125.33 125.94 131.49 134.43 137.76 142.33 145.77 149.78 154.01 162.53 166.99 171.4 180.77 183.04 190.89 202.4 218.72
+);
+
+@SI_R134a_ufg = qw(
+ 207.4 206.04 205.32 204.67 203.29 203.2 201.91 201.3 200.52 199.57 199.12 197.98 197.72 196.3 195.11 194.88 193.45 192.57 192.01 190.56 190.27 189.09 188.16 187.62 186.21 186.14 184.64 183.13 182.67 181.61 180.08 179.5 178.53 176.97 176.61 175.39 173.94 173.8 172.19 171.45 170.56 168.92 168.54 167.26 165.82 165.58 163.88 163.25 162.16 160.81 160.42 158.65 158.48 156.87 156.24 155.05 154.08 153.22 152 151.35 149.98 149.46 148.01 147.54 146.1 145.58 144.23 143.6 141.58 139.52 137.42 137.11 135.29 130.88 130.43 126.28 124.04 121.46 117.83 115.05 111.73 108.14 100.6 96.47 92.23 82.67 80.22 71.29 56.47 29.19
+);
+
+@SI_R134a_ug = qw(
+ 207.37 208.51 209.12 209.66 210.81 210.88 211.96 212.46 213.11 213.88 214.25 215.19 215.4 216.55 217.51 217.7 218.84 219.54 219.98 221.13 221.35 222.27 222.99 223.4 224.48 224.54 225.67 226.8 227.14 227.92 229.04 229.46 230.16 231.27 231.52 232.38 233.38 233.48 234.58 235.07 235.67 236.75 237 237.83 238.75 238.9 239.96 240.35 241.02 241.83 242.06 243.1 243.2 244.12 244.48 245.14 245.67 246.14 246.79 247.14 247.85 248.12 248.85 249.08 249.79 250.04 250.68 250.97 251.89 252.8 253.68 253.81 254.55 256.21 256.37 257.77 258.47 259.22 260.17 260.82 261.51 262.15 263.13 263.45 263.63 263.44 263.26 262.18 258.87 247.91
+);
+
+@SI_R134a_hf = qw(
+ 0 2.515 3.841 5.037 7.566 7.73 10.1 11.21 12.65 14.37 15.2 17.28 17.76 20.33 22.49 22.91 25.49 27.08 28.09 30.69 31.21 33.3 34.97 35.92 38.43 38.55 41.19 43.84 44.66 46.5 49.17 50.18 51.86 54.55 55.16 57.25 59.72 59.97 62.69 63.94 65.43 68.18 68.81 70.95 73.33 73.73 76.52 77.54 79.32 81.51 82.14 84.98 85.26 87.83 88.82 90.69 92.22 93.58 95.47 96.48 98.6 99.4 101.61 102.33 104.51 105.29 107.32 108.26 111.26 114.28 117.32 117.77 120.39 126.59 127.22 132.91 135.93 139.36 144.07 147.62 151.76 156.13 164.98 169.63 174.24 184.07 186.46 194.76 207.05 224.79
+);
+
+@SI_R134a_hfg = qw(
+ 225.86 224.61 223.95 223.35 222.09 222 220.81 220.25 219.52 218.65 218.22 217.16 216.92 215.59 214.48 214.26 212.91 212.08 211.55 210.18 209.9 208.79 207.9 207.38 206.03 205.96 204.52 203.07 202.62 201.6 200.11 199.54 198.6 197.07 196.71 195.51 194.08 193.94 192.35 191.62 190.73 189.09 188.71 187.42 185.98 185.73 184.01 183.38 182.27 180.9 180.49 178.69 178.51 176.85 176.21 174.99 173.98 173.08 171.82 171.14 169.71 169.17 167.66 167.16 165.64 165.1 163.67 163 160.86 158.67 156.43 156.1 154.14 149.39 148.9 144.38 141.93 139.1 135.11 132.02 128.33 124.32 115.85 111.16 106.35 95.44 92.63 82.35 65.21 33.58
+);
+
+@SI_R134a_hg = qw(
+ 225.86 227.12 227.79 228.39 229.65 229.73 230.91 231.46 232.17 233.02 233.43 234.44 234.68 235.92 236.97 237.17 238.41 239.16 239.64 240.87 241.11 242.09 242.86 243.3 244.46 244.51 245.72 246.91 247.28 248.1 249.28 249.72 250.45 251.61 251.88 252.77 253.81 253.91 255.04 255.55 256.16 257.27 257.53 258.37 259.3 259.46 260.53 260.92 261.59 262.4 262.64 263.67 263.77 264.68 265.03 265.68 266.2 266.66 267.29 267.62 268.31 268.57 269.26 269.49 270.15 270.39 270.99 271.27 272.12 272.95 273.75 273.87 274.53 275.98 276.12 277.3 277.86 278.46 279.17 279.64 280.09 280.46 280.82 280.79 280.59 279.51 279.09 277.11 272.26 258.37
+);
+
+@SI_R134a_sf = qw(
+ 0 0.01072 0.01634 0.02138 0.03199 0.03267 0.04253 0.04711 0.05301 0.06008 0.06344 0.07188 0.07382 0.08414 0.09275 0.09441 0.10463 0.11087 0.11481 0.12493 0.12693 0.13501 0.14139 0.14504 0.15457 0.15504 0.16498 0.17489 0.17794 0.18476 0.19459 0.19829 0.20439 0.21415 0.21637 0.22387 0.2327 0.23356 0.24323 0.24761 0.25286 0.26246 0.26465 0.27204 0.28023 0.28159 0.29112 0.29461 0.30063 0.30799 0.31011 0.31958 0.32051 0.32903 0.3323 0.33846 0.34345 0.34789 0.35404 0.3573 0.36413 0.3667 0.37377 0.37609 0.38301 0.38548 0.39189 0.39486 0.40425 0.41363 0.42302 0.42441 0.43242 0.45126 0.45315 0.47018 0.47911 0.4892 0.50294 0.5132 0.52509 0.53755 0.56241 0.57531 0.588 0.61473 0.62118 0.64336 0.67578 0.72217
+);
+
+@SI_R134a_sfg = qw(
+ 0.96866 0.95511 0.94807 0.94176 0.92859 0.92775 0.9156 0.90999 0.90278 0.89419 0.89012 0.87995 0.87762 0.86527 0.85503 0.85307 0.84101 0.83368 0.82908 0.81729 0.81496 0.80561 0.79826 0.79406 0.78316 0.78263 0.7713 0.76008 0.75664 0.74896 0.73794 0.73381 0.72701 0.71616 0.71369 0.7054 0.69566 0.69471 0.6841 0.67929 0.67356 0.66308 0.66069 0.65266 0.64377 0.6423 0.63198 0.62821 0.62172 0.61378 0.61149 0.6013 0.6003 0.59115 0.58763 0.58102 0.57567 0.57091 0.56431 0.56082 0.55349 0.55074 0.54315 0.54066 0.53323 0.53058 0.52368 0.52049 0.51039 0.50027 0.49012 0.48863 0.47993 0.45941 0.45734 0.43863 0.42873 0.41749 0.40204 0.39039 0.37675 0.36227 0.33272 0.31695 0.30111 0.26644 0.25776 0.22674 0.17711 0.08999
+);
+
+@SI_R134a_sg = qw(
+ 0.96866 0.96584 0.96441 0.96315 0.96058 0.96042 0.95813 0.9571 0.95579 0.95427 0.95356 0.95183 0.95144 0.94941 0.94779 0.94748 0.94564 0.94456 0.94389 0.94222 0.9419 0.94063 0.93965 0.93911 0.93773 0.93766 0.93629 0.93497 0.93458 0.93372 0.93253 0.9321 0.93139 0.93031 0.93006 0.92927 0.92836 0.92828 0.92733 0.92691 0.92641 0.92554 0.92535 0.9247 0.924 0.92389 0.9231 0.92282 0.92234 0.92177 0.9216 0.92088 0.92081 0.92018 0.91994 0.91948 0.91912 0.91879 0.91835 0.91811 0.91762 0.91743 0.91692 0.91675 0.91624 0.91606 0.91558 0.91536 0.91464 0.91391 0.91315 0.91303 0.91236 0.91067 0.9105 0.9088 0.90784 0.90669 0.90498 0.90359 0.90184 0.89982 0.89512 0.89226 0.88912 0.88117 0.87894 0.8701 0.85289 0.81215
+);
+
+##############################
+### ###
+### superheated R-134a ###
+### ###
+##############################
+
+#### ####
+#### 0.7 MPa ####
+#### ####
+
+@SI_superR134a_0p7MPa_T = qw(
+ 26.69 30 40 50 60 70 80 90 100 110 120 130 140 150 160
+);
+
+@SI_superR134a_0p7MPa_v = qw(
+ 0.029361 0.029966 0.031696 0.033322 0.034875 0.036373 0.037829 0.03925 0.040642 0.04201 0.043358 0.044688 0.046004 0.047306 0.048597
+);
+
+@SI_superR134a_0p7MPa_u = qw(
+ 244.48 247.48 256.39 265.2 274.01 282.87 291.8 300.82 309.95 319.19 328.55 338.04 347.66 357.41 367.29
+);
+
+@SI_superR134a_0p7MPa_h = qw(
+ 265.03 268.45 278.57 288.53 298.42 308.33 318.28 328.29 338.4 348.6 358.9 369.32 379.86 390.52 401.31
+);
+
+@SI_superR134a_0p7MPa_s = qw(
+ 0.9199 0.9313 0.9641 0.9954 1.0256 1.0549 1.0835 1.1114 1.1389 1.1658 1.1924 1.2186 1.2444 1.2699 1.2951
+);
+
+#### ####
+#### 0.8 MPa ####
+#### ####
+
+@SI_superR134a_0p8MPa_T = qw(
+ 31.31 40 50 60 70 80 90 100 110 120 130 140 150 160 170 180
+);
+
+@SI_superR134a_0p8MPa_v = qw(
+ 0.025621 0.027035 0.028547 0.029973 0.03134 0.032659 0.033941 0.035193 0.03642 0.037625 0.038813 0.039985 0.041143 0.04229 0.043427 0.044554
+);
+
+@SI_superR134a_0p8MPa_u = qw(
+ 246.79 254.82 263.86 272.83 281.81 290.84 299.95 309.15 318.45 327.87 337.4 347.06 356.85 366.76 376.81 386.99
+);
+
+@SI_superR134a_0p8MPa_h = qw(
+ 267.29 276.45 286.69 296.81 306.88 316.97 327.1 337.3 347.59 357.97 368.45 379.05 389.76 400.59 411.55 422.64
+);
+
+@SI_superR134a_0p8MPa_s = qw(
+ 0.9183 0.948 0.9802 1.011 1.0408 1.0698 1.0981 1.1258 1.153 1.1798 1.2061 1.2321 1.2577 1.283 1.308 1.3327
+);
+
+#### ####
+#### 0.9 MPa ####
+#### ####
+
+@SI_superR134a_0p9MPa_T = qw(
+ 35.51 40 50 60 70 80 90 100 110 120 130 140 150 160 170 180
+);
+
+@SI_superR134a_0p9MPa_v = qw(
+ 0.022683 0.023375 0.024809 0.026146 0.027413 0.02863 0.029806 0.030951 0.032068 0.033164 0.034241 0.035302 0.036349 0.037384 0.038408 0.039423
+);
+
+@SI_superR134a_0p9MPa_u = qw(
+ 248.85 253.13 262.44 271.6 280.72 289.86 299.06 308.34 317.7 327.18 336.76 346.46 356.28 366.23 376.31 386.52
+);
+
+@SI_superR134a_0p9MPa_h = qw(
+ 269.26 274.17 284.77 295.13 305.39 315.63 325.89 336.19 346.56 357.02 367.58 378.23 389 399.88 410.88 422
+);
+
+@SI_superR134a_0p9MPa_s = qw(
+ 0.9169 0.9327 0.966 0.9976 1.028 1.0574 1.086 1.114 1.1414 1.1684 1.1949 1.221 1.2467 1.2721 1.2972 1.3221
+);
+
+#### ####
+#### 1.2 MPa ####
+#### ####
+
+@SI_superR134a_1p2MPa_T = qw(
+ 46.29 50 60 70 80 90 100 110 120 130 140 150 160 170 180
+);
+
+@SI_superR134a_1p2MPa_v = qw(
+ 0.016715 0.017201 0.018404 0.019502 0.020529 0.021506 0.022442 0.023348 0.024228 0.025086 0.025927 0.026753 0.027566 0.028367 0.029158
+);
+
+@SI_superR134a_1p2MPa_u = qw(
+ 253.81 257.63 267.56 277.21 286.75 296.26 305.8 315.38 325.03 334.77 344.61 354.56 364.61 374.78 385.08
+);
+
+@SI_superR134a_1p2MPa_h = qw(
+ 273.87 278.27 289.64 300.61 311.39 322.07 332.73 343.4 354.11 364.88 375.72 386.66 397.69 408.82 420.07
+);
+
+@SI_superR134a_1p2MPa_s = qw(
+ 0.913 0.9267 0.9614 0.9938 1.0248 1.0546 1.0836 1.1118 1.1394 1.1664 1.193 1.2192 1.2449 1.2703 1.2954
+);
+
+1; #required at end of file - a perl thing
diff --git a/macros/math/SolveLinearEquationPCC.pl b/macros/math/SolveLinearEquationPCC.pl
new file mode 100644
index 0000000000..587916d8de
--- /dev/null
+++ b/macros/math/SolveLinearEquationPCC.pl
@@ -0,0 +1,168 @@
+#These three subroutines uniformize how all our "solve this equation" problems are handled.
+
+loadMacros('PCCmacros.pl');
+
+sub _init_SolveLinearEquationPCC {
+
+ #Possibly add initialization code here
+ #sub routine is not required, but prevents the macro from being re-loaded
+
+}
+
+sub contextSetup {
+ my ($vRef, $aRef) = @_;
+ Context("LimitedFraction");
+ Context()->flags->set(requirePureFractions => 1);
+ Context()->flags->set(showMixedNumbers => 0);
+ %varAddHash = ();
+ for my $i (0 .. $#{$vRef}) {
+ $varAddHash{ $vRef->[$i] } = 'Real';
+ }
+ Context()->variables->are(%varAddHash);
+ parser::Assignment->Allow;
+ Context()->flags->set(
+ reduceConstantFunctions => 0,
+ formatStudentAnswer => 'parsed',
+ reduceConstants => 0,
+ reduceFractions => 0,
+ showExtraParens => 0
+ );
+ Context()->operators->redefine(',');
+ Context()->lists->set(Set => { open => "{", close => "}" });
+ Context()->parens->set("{" => { type => "Set", close => "}" });
+ my @eqTypes = ();
+
+ for my $i (keys %varAddHash) {
+ push(@eqTypes, $i . ' = ___');
+ }
+ my $n = $#eqTypes;
+ my $eqTypesString = $eqTypes[0];
+ if ($n == 1) { $eqTypesString .= ' or ' . $eqTypes[1]; }
+ if ($n > 1) {
+ for my $i (1 .. $n - 1) { $eqTypesString .= ', ' . $eqTypes[$i]; }
+ $eqTypesString .= ', or ' . $eqTypes[$n];
+ }
+ Context()->{error}{msg}{"The left side of an assignment must be a variable or function"} =
+ "Your answer should be in the form $eqTypesString";
+ Context()->{error}{msg}{"The right side of an assignment must not include the variable being defined"} =
+ "The right side must not include the variable being defined";
+ Context()->strings->add(
+ "no solution" => {},
+ "no solutions" => { alias => 'no solution' },
+ "none" => { alias => 'no solution' },
+ "all real numbers" => {},
+ "infinite number of solutions" => { alias => 'all real numbers' },
+ "infinite" => { alias => 'all real numbers' },
+ "infinite solutions" => { alias => 'all real numbers' }
+ );
+ my @ansEqArray = ();
+ for my $i (0 .. $#{$aRef}) {
+ my $tempString = $vRef->[$i] . ' = ' . $aRef->[$i];
+ push(@ansEqArray, Formula("$tempString"));
+ }
+ @eqTypes = ();
+ for my $i (0 .. $#{$vRef}) {
+ push(@eqTypes, $vRef->[$i] . ' = ___');
+ }
+ return (\@ansEqArray, \@eqTypes);
+}
+
+sub instructions {
+ my ($vRef) = @_;
+ %varAddHash = ();
+ for my $i (0 .. $#{$vRef}) {
+ $varAddHash{ $vRef->[$i] } = 'Real';
+ }
+ my @eqTypes = ();
+ for my $i (keys %varAddHash) {
+ push(@eqTypes, '[|' . $i . ' = ___|]*');
+ }
+ my $eqTypesString = join(', ', @eqTypes);
+#"Solve the following linear equation; the answer could be in the form $eqTypesString, [|no solution|]*, or [|all real numbers|]*."
+ 'Solve the equation.'
+ . KeyboardInstructions(q! (The answer might be "[|no solution|]*" or "[|all real numbers|]*".)!);
+}
+
+sub answerCheck {
+ my ($ansEqRef, $eqTypesRef) = @_;
+ for my $i (0 .. $#{$ansEqRef}) {
+ $ansType[$i] = $eqTypesRef->[$i];
+ $ansEq[$i] = $ansEqRef->[$i];
+ ANS(
+ $ansEq[$i]->cmp(
+ cmp_class => "a variable assignment of the form $ansType[$i]",
+ correct_ans_latex_string => '\\{' . (Compute($ansEq[$i])->value)[1] . '\\}',
+ correct_ans => '{' . (Compute($ansEq[$i])->value)[1] . '}',
+ )->withPostFilter(AnswerHints(
+#(Compute($ansEq[$i])->value)[1] => ["You have the solution, but the answer to this question should be in the form $ansType[$i]" , replaceMessage => 1],
+#sub {
+# my ($correct, $student, $ansHash) =@_;
+# return (Compute($ansHash->{original_student_ans})->class eq 'Set');
+#} => ["You are trying to give the solution set in set notation, but the answer to this question should be in the form $ansType[$i]" , replaceMessage => 1],
+#sub {
+# my ($correct, $student, $ansHash) =@_;
+# return (Compute($ansHash->{original_student_ans})->class eq 'Set' and $student == (Compute($ansEq[$i])->value)[1]);
+#} => ["You have the solution set, but the answers to this question should be in the form $ansType[$i]" , replaceMessage => 1],
+ $ansEq[$i] => [
+ "Good. And the solution set for this equation is {" . (Compute($ansEq[$i])->value)[1] . "}.",
+ replaceMessage => 1,
+ checkCorrect => 1
+ ],
+ ))->withPostFilter(sub {
+ my $ansHash = shift;
+ if ($ansHash->{score}) {
+ my ($svar, $sfrac) = $ansHash->{student_value}->value;
+ if (Value::classMatch($sfrac, 'Fraction')) {
+ if (($sfrac->value)[1] == 1 or ($sfrac->value)[0] == 0) {
+ $ansHash->{score} = 0;
+ $ansHash->{ans_message} = "Your fraction is not reduced";
+ } else {
+ my $correctAnswer = $ansHash->{correct_value};
+ my ($cvar, $cfrac) = Compute("$correctAnswer")->value;
+ $cfrac = Fraction($cfrac);
+ my $check = $cfrac->cmp->evaluate($sfrac->string);
+ $ansHash->{score} = $check->{score};
+ $ansHash->{ans_message} = $check->{ans_message};
+ }
+ }
+ }
+#if we make it through all the pre-2017 answer checking, now check for answers like 12 or {12} (as opposed to x=12) and award credit
+ elsif (Value::classMatch($ansHash->{student_value}, 'Fraction')
+ or Value::classMatch($ansHash->{student_value}, 'Real'))
+ {
+ my $simpleans = Fraction((Compute($ansEq[$i])->value)[1]);
+ my $simplecheck = $simpleans->cmp->evaluate($ansHash->{student_value}->string);
+ $ansHash->{score} = $simplecheck->{score};
+ $ansHash->{ans_message} = $simplecheck->{ans_message};
+ if (Value::classMatch($ansHash->{student_value}, 'Fraction')) {
+ if (($ansHash->{student_value}->value)[1] == 1
+ or ($ansHash->{student_value}->value)[0] == 0)
+ {
+ $ansHash->{score} = 0;
+ $ansHash->{ans_message} = "Your fraction is not reduced";
+ }
+ }
+ } elsif (Value::classMatch($ansHash->{student_value}, 'Set')) {
+ my $correctsol = (Compute($ansEq[$i])->value)[1];
+ my $check = $correctsol->cmp->evaluate(($ansHash->{student_value}->value)[0]->string);
+ $ansHash->{score} = $check->{score};
+ $ansHash->{ans_message} = $check->{ans_message};
+ }
+ return $ansHash;
+ })
+ );
+ }
+}
+
+sub summary {
+ my ($ansEq, $left, $right) = @_;
+ my ($var, $ans) = (Compute($ansEq)->value);
+ my $ansEqTeX = $ansEq->TeX;
+ my $leftTeX = $left->TeX;
+ my $rightTeX = $right->TeX;
+ my $ansTeX = $ans->TeX;
+ "The solution to this equation is [`$ans`]. To stress that this is a value assigned to [`$var`], some report [`$ansEqTeX`]. We can also say that the solution set is [`\\{$ansTeX\\}`], or that [`$var\\in\\left\\{$ansTeX\\right\\}`]. If we substitute [`$ansTeX`] in for [`$var`] in the original equation [`$leftTeX=$rightTeX`], the equation will be true. Please check this on your own; it is an important habit. ";
+
+}
+
+1;
diff --git a/macros/math/SystemsOfLinearEquationsProblemPCC.pl b/macros/math/SystemsOfLinearEquationsProblemPCC.pl
new file mode 100644
index 0000000000..2294938560
--- /dev/null
+++ b/macros/math/SystemsOfLinearEquationsProblemPCC.pl
@@ -0,0 +1,841 @@
+
+loadMacros('PCCmacros.pl', 'contextFraction.pl');
+
+sub SystemOfLinearEquationsProblemSetup {
+
+ #markers for zero-values used in formatting
+ $zeroa = ($a == Fraction(0, 1)) ? 0 : 1;
+ $zerob = ($b == Fraction(0, 1)) ? 0 : 1;
+ $zeroc = ($c == Fraction(0, 1)) ? 0 : 1;
+ $zerod = ($d == Fraction(0, 1)) ? 0 : 1;
+ $zeroe = ($e == Fraction(0, 1)) ? 0 : 1;
+ $zerof = ($f == Fraction(0, 1)) ? 0 : 1;
+
+#How will the equations be displayed?
+# This is tricky - we can't use Formula reductions or the fractions will be converted to decimals. So special treatment is given to "0x", "1x", and "-1x". Also, care is taken to prevent "--x" from becoming "+x" when it should just be "x".
+ Context()->flags->set(reduceConstants => 0, showExtraParens => 0);
+
+ $asided = $a * (-1)**$xside1;
+ $bsided = $b * (-1)**$yside1;
+ $csided = $c * (-1)**$xside2;
+ $dsided = $d * (-1)**$yside2;
+ $esided = $e * (-1)**$cside1;
+ $fsided = $f * (-1)**$cside2;
+
+ Context()->variables->are($x => 'Real', $y => 'Real', constant => 'Real');
+
+ ($left1, $right1, $left2, $right2, $leftOrd1, $rightOrd1, $leftOrd2, $rightOrd2) = makeSides(
+ $asided, $bsided, $csided, $dsided, $esided, $fsided, $xside1, $yside1,
+ $cside1, $xside2, $yside2, $cside2, $zeroa, $zerob, $zeroc, $zerod,
+ $zeroe, $zerof, $x, $y, "constant", 1, [ 0, 1, 2 ], [ 0, 1, 2 ],
+ [ 0, 1, 2 ], [ 0, 1, 2 ]
+ );
+
+ # How to enter answers?
+ $EnterAnswers =
+ (($x eq "x" and $y eq "y") or ($x eq "y" and $y eq "x"))
+ ? "Give your answer as an ordered pair or in the form *x=[|___|] and y=[|___|] *."
+ : "Give your answer in the form *$x = [|___|] and $y = [|___|] *.";
+ $EnterAnswers = $EnterAnswers
+ . " If there are no solutions, you may enter *no solutions* or *none*. If there are infinitely many solutions, you may enter *infinitely many solutions*.";
+ $EnterAnswers = KeyboardInstructions($EnterAnswers);
+
+ $determinant = (($a) * ($d) - ($b) * ($c));
+ if ($determinant != 0) {
+ $xsol = (($d) * (-$e) + (-$b) * (-$f)) / $determinant;
+ $ysol = ((-$c) * (-$e) + ($a) * (-$f)) / $determinant;
+ }
+}
+
+sub shuffle {
+ my $array = shift;
+ my $i;
+ for my $i (0 .. @$array - 1) {
+ my $j = random($i, @$array - 1, 1);
+ next if ($i == $j);
+ @$array[ $i, $j ] = @$array[ $j, $i ];
+ }
+}
+
+sub makeSides {
+ my (
+ $Asided, $Bsided, $Csided, $Dsided, $Esided, $Fsided, $Xside1,
+ $Yside1, $Cside1, $Xside2, $Yside2, $Cside2, $Zeroa, $Zerob,
+ $Zeroc, $Zerod, $Zeroe, $Zerof, $X, $Y, $Constant,
+ $Shuffle, $LeftOrder1, $RightOrder1, $LeftOrder2, $RightOrder2
+ ) = @_;
+
+ Context()->normalStrings;
+ my ($ax, $by, $cx, $dy);
+
+ if ($Asided == Fraction(1, 1)) { $ax = Formula("$X") }
+ elsif ($Asided == Fraction(-1, 1)) { $ax = Formula("-$X") }
+ else { $ax = Formula("$Asided $X") }
+ if ($Bsided == Fraction(1, 1)) { $by = Formula("$Y") }
+ elsif ($Bsided == Fraction(-1, 1)) { $by = Formula("-$Y") }
+ else { $by = Formula("$Bsided $Y") }
+ if ($Csided == Fraction(1, 1)) { $cx = Formula("$X") }
+ elsif ($Csided == Fraction(-1, 1)) { $cx = Formula("-$X") }
+ else { $cx = Formula("$Csided $X") }
+ if ($Dsided == Fraction(1, 1)) { $dy = Formula("$Y") }
+ elsif ($Dsided == Fraction(-1, 1)) { $dy = Formula("-$Y") }
+ else { $dy = Formula("$Dsided $Y") }
+
+ my @leftTerms1 = ("(1-$Xside1)*$X*$Zeroa", "(1-$Yside1)*$Y*$Zerob", "(1-$Cside1)*$Constant*$Zeroe");
+ my @rightTerms1 = ("$Xside1*$X*$Zeroa", "$Yside1*$Y*$Zerob", "$Cside1*$Constant*$Zeroe");
+ my @leftTerms2 = ("(1-$Xside2)*$X*$Zeroc", "(1-$Yside2)*$Y*$Zerod", "(1-$Cside2)*$Constant*$Zerof");
+ my @rightTerms2 = ("$Xside2*$X*$Zeroc", "$Yside2*$Y*$Zerod", "$Cside2*$Constant*$Zerof");
+
+ if ($Shuffle) {
+ shuffle($LeftOrder1);
+ shuffle($RightOrder1);
+ shuffle($LeftOrder2);
+ shuffle($RightOrder2);
+ }
+
+ my $Left1 =
+ Formula("$leftTerms1[${$LeftOrder1}[0]]+$leftTerms1[${$LeftOrder1}[1]]+$leftTerms1[${$LeftOrder1}[2]]")->reduce;
+ my $Right1 =
+ Formula("$rightTerms1[${$RightOrder1}[0]]+$rightTerms1[${$RightOrder1}[1]]+$rightTerms1[${$RightOrder1}[2]]")
+ ->reduce;
+ $Left1 = $Left1->substitute($X => $ax, $Y => $by, $Constant => Formula("$Esided"));
+ $Right1 = $Right1->substitute($X => $ax, $Y => $by, $Constant => Formula("$Esided"));
+
+ $Left2 =
+ Formula("$leftTerms2[${$LeftOrder2}[0]]+$leftTerms2[${$LeftOrder2}[1]]+$leftTerms2[${$LeftOrder2}[2]]")->reduce;
+ $Right2 =
+ Formula("$rightTerms2[${$RightOrder2}[0]]+$rightTerms2[${$RightOrder2}[1]]+$rightTerms2[${$RightOrder2}[2]]")
+ ->reduce;
+ $Left2 = $Left2->substitute($X => $cx, $Y => $dy, $Constant => Formula("$Fsided"));
+ $Right2 = $Right2->substitute($X => $cx, $Y => $dy, $Constant => Formula("$Fsided"));
+
+ Context()->texStrings;
+
+ return ($Left1, $Right1, $Left2, $Right2, $LeftOrder1, $RightOrder1, $LeftOrder2, $RightOrder2);
+}
+
+sub SystemOfLinearEquationsProblemSolutionSetup {
+
+ $Attack = ""; #the steps to solve the problem.
+
+##############################################
+ #If any of the coefficients are 0...
+ Context()->texStrings;
+ if ($a == Fraction(0, 1)) {
+ $ysol1 = -$e / $b;
+ if ($c == Fraction(0, 1)) {
+ $ysol2 = -$f / $d;
+ if ($ysol1 == $ysol2) {
+ $Attack = $Attack
+ . "These two equations both say that [`$y=$ysol1`] and say nothing about [`$x`]. There are infinitely many solutions to the system: [`$x`] can be any real number, paired up with [`$y=$ysol1`].";
+ } else {
+ $Attack = $Attack
+ . "The first equation says that [`$y=$ysol1`] and the second says that [`$y=$ysol2`]. These cannot both simultaneously be true, so the system has no solutions.";
+ }
+ } elsif ($d == Fraction(0, 1)) {
+ $xsol2 = -$f / $c;
+ $Attack = $Attack
+ . "The first equation says that [`$y=$ysol1`] and the second says that [`$x=$xsol2`]. So there is a unique solution: [`$x=$xsol2, $y=$ysol1`]";
+ } else {
+ $Attack = $Attack
+ . " The first equation immediately allows us to solve for [`$y`]: [`$y = $ysol`]. We can substitute this into the second equation to solve for [`$x`] to find that [`$x=$xsol`].
+
+";
+
+ }
+ } elsif ($b == Fraction(0, 1)) {
+ $xsol1 = -$e / $a;
+ if ($d == Fraction(0, 1)) {
+ $xsol2 = -$f / $c;
+ if ($xsol1 == $xsol2) {
+ $Attack = $Attack
+ . "These two equations both say that [`$x=$xsol1`] and say nothing about [`$y`]. There are infinitely many solutions to the system: [`$y`] can be any real number, paired up with [`$x=$xsol1`].";
+ } else {
+ $Attack = $Attack
+ . "The first equation says that [`$x=$xsol1`] and the second says that [`$x=$xsol2`]. These cannot both simultaneously be true, so the system has no solutions.";
+ }
+ } elsif ($c == Fraction(0, 1)) {
+ $ysol2 = -$f / $d;
+ $Attack = $Attack
+ . "The first equation says that [`$x=$xsol1`] and the second says that [`$y=$ysol2`]. So there is a unique solution: [`$x=$xsol1, $y=$ysol2`]";
+ } else {
+ $Attack = $Attack
+ . " The first equation immediately allows us to solve for [`$x`]: [`$x = $xsol`]. We can substitute this into the second equation to solve for [`$y`] to find that [`$y=$ysol`].
+
+";
+
+ }
+ } elsif ($c == Fraction(0, 1)) {
+ $Attack = $Attack
+ . " The second equation immediately allows us to solve for [`$y`]: [`$y = $ysol`]. We can substitute this into the second equation to solve for [`$x`] to find that [`$x=$xsol`].
+
+";
+ } elsif ($d == Fraction(0, 1)) {
+ $Attack = $Attack
+ . " The second equation immediately allows us to solve for [`$x`]: [`$x = $xsol`]. We can substitute this into the second equation to solve for [`$y`] to find that [`$y=$ysol`].
+
+";
+ } else {
+
+##############################################
+ #Now assuming no coefficients are 0...
+
+##############################################
+ #clear denominators, if necessary
+
+ $aden = ($a->value)[1];
+ $bden = ($b->value)[1];
+ $cden = ($c->value)[1];
+ $dden = ($d->value)[1];
+ $eden = ($e->value)[1];
+ $fden = ($f->value)[1];
+
+ $lcd1 = lcm($aden, $bden);
+ $lcd1 = lcm($lcd1, $eden);
+ $lcd2 = lcm($cden, $dden);
+ $lcd2 = lcm($lcd2, $fden);
+
+ if ($lcd1 == 1 and $lcd2 == 1) {
+ $Attack = $Attack . "These equations have no fractions; let's try to keep it that way.";
+ } else {
+ $Attack = $Attack
+ . "If an equation involves fractions, it is helpful to clear denominators by multiplying both sides of the equation by a common multiple of the denominators.";
+ }
+
+ if ($lcd1 == 1 and $lcd2 == 1) {
+ } elsif ($lcd1 == 1) {
+ $Attack = $Attack . "
+
+ [```\\left\\{\\begin{aligned}
+ $left1 & = $right1\\\\
+ $lcd2\\left($left2\\right) & = $lcd2\\left($right2\\right)
+ \\end{aligned}\\right.```]
+
+ ";
+ } elsif ($lcd2 == 1) {
+ $Attack = $Attack . "
+
+ [```\\left\\{\\begin{aligned}
+ $lcd1\\left($left1\\right) & = $lcd1\\left($right1\\right)\\\\
+ $left2 & = $right2
+ \\end{aligned}\\right.```]
+
+ ";
+ } else {
+ $Attack = $Attack . "
+
+ [```\\left\\{\\begin{aligned}
+ $lcd1\\left($left1\\right) & = $lcd1\\left($right1\\right)\\\\
+ $lcd2\\left($left2\\right) & = $lcd2\\left($right2\\right)
+ \\end{aligned}\\right.```]
+
+ ";
+ }
+
+ $asided = $asided * $lcd1;
+ $bsided = $bsided * $lcd1;
+ $csided = $csided * $lcd2;
+ $dsided = $dsided * $lcd2;
+ $esided = $esided * $lcd1;
+ $fsided = $fsided * $lcd2;
+
+ ($left1, $right1, $left2, $right2, $leftOrd1, $rightOrd1, $leftOrd2, $rightOrd2) = makeSides(
+ $asided, $bsided, $csided, $dsided, $esided, $fsided, $xside1, $yside1,
+ $cside1, $xside2, $yside2, $cside2, $zeroa, $zerob, $zeroc, $zerod,
+ $zeroe, $zerof, $x, $y, "constant", 0, $leftOrd1, $rightOrd1,
+ $leftOrd2, $rightOrd2
+ );
+
+ $Attack = $Attack . "
+
+ [```\\left\\{
+ \\begin{aligned}
+ $left1 & = $right1\\\\
+ $left2 & = $right2
+ \\end{aligned}
+ \\right.```]
+
+";
+
+ # Does any equation have 1 or -1 as a coefficient? Use substitution.
+
+#############################################################
+ #if no coefficient is 1, and some coefficient is -1 then flip that term to the other side.
+
+ if ($asided != Fraction(1, 1)
+ and $bsided != Fraction(1, 1)
+ and $csided != Fraction(1, 1)
+ and $dsided != Fraction(1, 1))
+ {
+ $swap = 0;
+ if ($asided == Fraction(-1, 1)) {
+ $xside1 = 1 - $xside1;
+ $asided = $asided * (-1);
+ $swap = 1;
+ } elsif ($bsided == Fraction(-1, 1)) {
+ $yside1 = 1 - $yside1;
+ $bsided = $bsided * (-1);
+ $swap = 1;
+ } elsif ($csided == Fraction(-1, 1)) {
+ $xside2 = 1 - $xside2;
+ $csided = $csided * (-1);
+ $swap = 1;
+ } elsif ($dsided == Fraction(-1, 1)) {
+ $yside2 = 1 - $yside2;
+ $dsided = $dsided * (-1);
+ $swap = 1;
+ }
+
+ if ($swap == 1) {
+ ($left1, $right1, $left2, $right2, $leftOrd1, $rightOrd1, $leftOrd2, $rightOrd2) = makeSides(
+ $asided, $bsided, $csided, $dsided, $esided, $fsided,
+ $xside1, $yside1, $cside1, $xside2, $yside2, $cside2,
+ $zeroa, $zerob, $zeroc, $zerod, $zeroe, $zerof,
+ $x, $y, "constant", 0, $leftOrd1, $rightOrd1,
+ $leftOrd2, $rightOrd2
+ );
+
+ $Attack = $Attack
+ . "Since there is a coefficient of [`-1`], it is wise to add the corresponding term to both sides of that equation, so that we have a coefficient of [`1`] to work with.
+
+ [```\\left\\{
+ \\begin{aligned}
+ $left1 & = $right1\\\\
+ $left2 & = $right2
+ \\end{aligned}
+ \\right.```]
+
+";
+ }
+ }
+
+#############################################################
+#if only the second equation has a 1 for one of its coefficients, swap equations; this is more to simplify the code of the solution.
+
+ if ($asided != Fraction(1, 1)
+ and $bsided != Fraction(1, 1)
+ and ($csided == Fraction(1, 1) or $dsided == Fraction(1, 1)))
+ {
+ ($a, $b, $c, $d, $e, $f) = ($c, $d, $a, $b, $f, $e);
+
+ ($lcd1, $lcd2) = ($lcd2, $lcd1);
+
+ ($zeroa, $zeroc) = ($zeroc, $zeroa);
+ ($zerob, $zerod) = ($zerod, $zerob);
+ ($zeroe, $zerof) = ($zerof, $zeroe);
+
+ ($xside1, $yside1, $cside1, $xside2, $yside2, $cside2) =
+ ($xside2, $yside2, $cside2, $xside1, $yside1, $cside1);
+
+ ($asided, $csided) = ($csided, $asided);
+ ($bsided, $dsided) = ($dsided, $bsided);
+ ($esided, $fsided) = ($fsided, $esided);
+
+ ($leftOrd1, $rightOrd1, $leftOrd2, $rightOrd2) = ($leftOrd2, $rightOrd2, $leftOrd1, $rightOrd1);
+
+ ($left1, $right1, $left2, $right2, $leftOrd1, $rightOrd1, $leftOrd2, $rightOrd2) = makeSides(
+ $asided, $bsided, $csided, $dsided, $esided, $fsided, $xside1, $yside1,
+ $cside1, $xside2, $yside2, $cside2, $zeroa, $zerob, $zeroc, $zerod,
+ $zeroe, $zerof, $x, $y, "constant", 0, $leftOrd1, $rightOrd1,
+ $leftOrd2, $rightOrd2
+ );
+
+ $Attack = $Attack
+ . " Since the second equation has a coefficient of [`1`], it is convenient to change the order of the equations.
+
+ [```\\left\\{\\begin{aligned}
+ $left1 & = $right1\\\\
+ $left2 & = $right2
+ \\end{aligned}\\right.```]
+
+";
+
+ }
+
+#############################################################
+ #If the first equation has 1x on either side...
+
+ if ($asided == Fraction(1, 1)) {
+ ($xside1, $yside1, $cside1) = ($xside1, 1 - $xside1, 1 - $xside1);
+ $asided = $a * $lcd1 * (-1)**$xside1;
+ $bsided = $b * $lcd1 * (-1)**$yside1;
+ $esided = $e * $lcd1 * (-1)**$cside1;
+
+ ($leftOrd1, $rightOrd1) = ([ 0, 1, 2 ], [ 0, 1, 2 ]);
+
+ ($left1, $right1, $left2, $right2, $leftOrd1, $rightOrd1, $leftOrd2, $rightOrd2) = makeSides(
+ $asided, $bsided, $csided, $dsided, $esided, $fsided, $xside1, $yside1,
+ $cside1, $xside2, $yside2, $cside2, $zeroa, $zerob, $zeroc, $zerod,
+ $zeroe, $zerof, $x, $y, "constant", 0, $leftOrd1, $rightOrd1,
+ $leftOrd2, $rightOrd2
+ );
+
+ $left1 = $left1->reduce;
+ $right1 = $right1->reduce;
+ $left2 = $left2->reduce;
+ $right2 = $right2->reduce;
+ $left2 = ($xside1 == 0) ? $left2->substitute($x => $right1) : $left2->substitute($x => $left1);
+ $right2 = ($xside1 == 0) ? $right2->substitute($x => $right1) : $right2->substitute($x => $left1);
+ Context()->normalStrings;
+ $left2 = Formula($left2);
+ $right2 = Formula($right2);
+ Context()->texStrings;
+ $leftStepLinear = $left2->D("$y")->reduce;
+ $leftStepConstant = $left2->eval($y => 0)->reduce;
+ $rightStepLinear = $right2->D("$y")->reduce;
+ $rightStepConstant = $right2->eval($y => 0)->reduce;
+ $leftStep = Formula("$leftStepLinear $y + $leftStepConstant")->reduce;
+ $rightStep = Formula("$rightStepLinear $y + $rightStepConstant")->reduce;
+ $leftNextStep = Formula("($leftStepLinear-$rightStepLinear)$y")->reduce;
+ $rightNextStep = Formula("$rightStepConstant-$leftStepConstant")->reduce;
+ Context()->flags->set(showExtraParens => 1);
+ $Attack = $Attack .
+
+ "Since one of the coefficients of [`$x`] is [`1`], it is wise to solve for the [`$x`] in terms of the other variable and then use substitution to complete the problem.
+
+ [```
+ \\begin{aligned}
+ $left1 & = $right1 &\\text{(from the first equation)}
+ \\end{aligned}
+ ```]
+
+which we can substitute in for [`$x`] into the second equation:
+
+ [```\\begin{aligned}
+ $left2 & = $right2 &\\text{(from the second equation)}\\\\
+ $leftStep & = $rightStep\\\\";
+
+ Context()->flags->set(showExtraParens => 0);
+ if ($leftStep ne $leftNextStep or $rightStep ne $rightNextStep) {
+ $Attack = $Attack . "$leftNextStep & = $rightNextStep\\\\";
+ }
+
+ if ($determinant == 0 and ($leftNextStep != $rightNextStep)) {
+ $Attack = $Attack . "\\end{aligned}```]
+
+Since this is a false equation no matter what the values of [`$x`] and [`$y`] are, the system has no solutions.";
+ } elsif ($determinant == 0 and ($leftNextStep == $rightNextStep)) {
+ $Attack = $Attack . "\\end{aligned}```]
+
+Since this is a true equation no matter what the values of [`$x`] and [`$y`] are, the system has infinitely many solutions - just pick any value for [`$y`] and then let [`$left1 = $right1`].";
+ } else {
+ $Attack = $Attack . "
+ $y &= $ysol
+ \\end{aligned}
+ ```]";
+
+ Context()->normalStrings;
+ $left1 = Formula($left1);
+ $right1 = Formula($right1);
+ Context()->texStrings;
+ $leftStepConstant = $left1->eval($y => 0, $x => 0)->reduce;
+ $rightStepConstant = $right1->eval($y => 0, $x => 0)->reduce;
+ $leftStepLinear = Fraction($left1->D("$y")->eval($x => 0), 1) * $ysol;
+ $rightStepLinear = Fraction($right1->D("$y")->eval($x => 0), 1) * $ysol;
+ Context()->normalStrings;
+ $leftStep = ($xside1 == 0) ? $left1 : Formula("$leftStepLinear+$leftStepConstant");
+ $rightStep = ($xside1 == 1) ? $right1 : Formula("$rightStepLinear+$rightStepConstant");
+ $leftNextStep = ($xside1 == 0) ? $x : $xsol;
+ $rightNextStep = ($xside1 == 1) ? $x : $xsol;
+
+ Context()->texStrings;
+
+ $Attack = $Attack . "
+
+We can substitute this for [`$y`] back into the first equation to find [`$x`].";
+ $right1 = "$bsided\\left($ysol\\right)+$esided" if ($xside1 == 0);
+ $left1 = "$bsided\\left($ysol\\right)+$esided" if ($xside1 == 1);
+ $Attack = $Attack . "
+
+ [```
+ \\begin{aligned}
+ $left1 & = $right1 &\\text{(from the first equation, after we had solved for }$x\\text{ in terms of} $y\\text{)}\\\\
+ $leftStep &= $rightStep\\\\
+ $leftNextStep &= $rightNextStep
+ \\end{aligned}
+ ```]
+
+So the solution is [`$x=$xsol, $y=$ysol`].";
+
+ }
+ }
+#############################################################
+ #If the first equation has 1y on either side...
+ elsif ($bsided == Fraction(1, 1)) {
+ ($xside1, $yside1, $cside1) = (1 - $yside1, $yside1, 1 - $yside1);
+ $asided = $a * $lcd1 * (-1)**$xside1;
+ $bsided = $b * $lcd1 * (-1)**$yside1;
+ $esided = $e * $lcd1 * (-1)**$cside1;
+
+ ($leftOrd1, $rightOrd1) = ([ 0, 1, 2 ], [ 0, 1, 2 ]);
+
+ ($left1, $right1, $left2, $right2, $leftOrd1, $rightOrd1, $leftOrd2, $rightOrd2) = makeSides(
+ $asided, $bsided, $csided, $dsided, $esided, $fsided, $xside1, $yside1,
+ $cside1, $xside2, $yside2, $cside2, $zeroa, $zerob, $zeroc, $zerod,
+ $zeroe, $zerof, $x, $y, "constant", 0, $leftOrd1, $rightOrd1,
+ $leftOrd2, $rightOrd2
+ );
+
+ $left1 = $left1->reduce;
+ $right1 = $right1->reduce;
+ $left2 = $left2->reduce;
+ $right2 = $right2->reduce;
+ $left2 = ($yside1 == 0) ? $left2->substitute($y => $right1) : $left2->substitute($y => $left1);
+ $right2 = ($yside1 == 0) ? $right2->substitute($y => $right1) : $right2->substitute($y => $left1);
+ Context()->normalStrings;
+ $left2 = Formula($left2);
+ $right2 = Formula($right2);
+ Context()->texStrings;
+ $leftStepLinear = $left2->D("$x")->reduce;
+ $leftStepConstant = $left2->eval($x => 0)->reduce;
+ $rightStepLinear = $right2->D("$x")->reduce;
+ $rightStepConstant = $right2->eval($x => 0)->reduce;
+ $leftStep = Formula("$leftStepLinear $x + $leftStepConstant")->reduce;
+ $rightStep = Formula("$rightStepLinear $x + $rightStepConstant")->reduce;
+ $leftNextStep = Formula("($leftStepLinear-$rightStepLinear)$x")->reduce;
+ $rightNextStep = Formula("$rightStepConstant-$leftStepConstant")->reduce;
+ Context()->flags->set(showExtraParens => 1);
+ $Attack = $Attack .
+
+ "Since one of the coefficients of [`$y`] is [`1`], it is wise to solve for [`$y`] in terms of the other variable and then use substitution to complete the problem.
+
+ [```
+ \\begin{aligned}
+ $left1 & = $right1 &\\text{(from the first equation)}
+ \\end{aligned}
+ ```]
+
+which we can substitute in for [`$y`] into the second equation:
+
+ [```\\begin{aligned}
+ $left2 & = $right2 &\\text{(from the second equation)}\\\\
+ $leftStep & = $rightStep\\\\";
+
+ Context()->flags->set(showExtraParens => 0);
+ if ($leftStep ne $leftNextStep or $rightStep ne $rightNextStep) {
+ $Attack = $Attack . "$leftNextStep & = $rightNextStep\\\\";
+ }
+
+ Context()->flags->set(showExtraParens => 1);
+ if ($determinant == 0 and ($leftNextStep != $rightNextStep)) {
+ $Attack = $Attack . "\\end{aligned}```]
+
+Since this is a false equation no matter what the values of [`$x`] and [`$y`] are, the system has no solutions.";
+ } elsif ($determinant == 0 and ($leftNextStep == $rightNextStep)) {
+ $Attack = $Attack . "\\end{aligned}```]
+
+Since this is a true equation no matter what the values of [`$x`] and [`$y`] are, the system has infinitely many solutions - just pick any value for [`$x`] and then let [`$left1 = $right1`].";
+ } else {
+ $Attack = $Attack . "
+ $x &= $xsol
+ \\end{aligned}
+ ```]";
+
+ Context()->normalStrings;
+ $left1 = Formula($left1);
+ $right1 = Formula($right1);
+ Context()->texStrings;
+ $leftStepConstant = $left1->eval($y => 0, $x => 0)->reduce;
+ $rightStepConstant = $right1->eval($y => 0, $x => 0)->reduce;
+ $leftStepLinear = Fraction($left1->D("$x")->eval($y => 0), 1) * $xsol;
+ $rightStepLinear = Fraction($right1->D("$x")->eval($y => 0), 1) * $xsol;
+ Context()->normalStrings;
+ $leftStep = ($yside1 == 0) ? $left1 : Formula("$leftStepLinear+$leftStepConstant");
+ $rightStep = ($yside1 == 1) ? $right1 : Formula("$rightStepLinear+$rightStepConstant");
+ $leftNextStep = ($yside1 == 0) ? $y : $ysol;
+ $rightNextStep = ($yside1 == 1) ? $y : $ysol;
+
+ Context()->texStrings;
+
+ $Attack = $Attack . "
+
+We can substitute this back for [`$x`] into the first equation to find [`$y`].";
+ $right1 = "$asided\\left($xsol\\right)+$esided" if ($yside1 == 0);
+ $left1 = "$asided\\left($xsol\\right)+$esided" if ($yside1 == 1);
+ $Attack = $Attack . "
+
+ [```
+ \\begin{aligned}
+ $left1 & = $right1 &\\text{(from the first equation, after we had solved for }$y\\text{ in terms of }$x\\text{)}\\\\
+ $leftStep &= $rightStep\\\\
+ $leftNextStep &= $rightNextStep
+ \\end{aligned}
+ ```]
+
+So the solution is [`$x=$xsol, $y=$ysol`].";
+
+ }
+ }
+#############################################################
+ #If no coefficient is 1 or -1, we use elimination
+ else {
+ ($xside1, $yside1, $cside1) = (0, 0, 1);
+ ($xside2, $yside2, $cside2) = (0, 0, 1);
+
+ $asided = $a * $lcd1 * (-1)**$xside1;
+ $bsided = $b * $lcd1 * (-1)**$yside1;
+ $csided = $c * $lcd2 * (-1)**$xside2;
+ $dsided = $d * $lcd2 * (-1)**$yside2;
+ $esided = $e * $lcd1 * (-1)**$cside1;
+ $fsided = $f * $lcd2 * (-1)**$cside2;
+
+ $lcmx = lcm(int("$asided"), int("$csided"));
+ $lcmy = lcm(int("$bsided"), int("$dsided"));
+
+ $eliminateYscalar1 = Real(int($lcmy / $bsided));
+ $eliminateYscalar2 = -Real(int($lcmy / $dsided));
+
+ $eliminateXscalar1 = Real(int($lcmx / $asided));
+ $eliminateXscalar2 = -Real(int($lcmx / $csided));
+
+ ($left1, $right1, $left2, $right2, $leftOrd1, $rightOrd1, $leftOrd2, $rightOrd2) = makeSides(
+ $asided, $bsided, $csided, $dsided, $esided, $fsided, $xside1, $yside1,
+ $cside1, $xside2, $yside2, $cside2, $zeroa, $zerob, $zeroc, $zerod,
+ $zeroe, $zerof, $x, $y, "constant", 0, $leftOrd1, $rightOrd1,
+ $leftOrd2, $rightOrd2
+ );
+
+ $left1Step = ($eliminateYscalar1 == 1) ? $left1 : "$eliminateYscalar1\\left($left1\\right)";
+ $right1Step = ($eliminateYscalar1 == 1) ? $right1 : "$eliminateYscalar1\\left($right1\\right)";
+ $left2Step = ($eliminateYscalar2 == 1) ? $left2 : "$eliminateYscalar2\\left($left2\\right)";
+ $right2Step = ($eliminateYscalar2 == 1) ? $right2 : "$eliminateYscalar2\\left($right2\\right)";
+
+ (
+ $left1NextStep, $right1NextStep, $left2NextStep, $right2NextStep, $leftOrd1, $rightOrd1, $leftOrd2,
+ $rightOrd2
+ )
+ = makeSides(
+ $asided * $eliminateYscalar1, $bsided * $eliminateYscalar1,
+ $csided * $eliminateYscalar2, $dsided * $eliminateYscalar2,
+ $esided * $eliminateYscalar1, $fsided * $eliminateYscalar2,
+ $xside1, $yside1,
+ $cside1, $xside2,
+ $yside2, $cside2,
+ $zeroa, $zerob,
+ $zeroc, $zerod,
+ $zeroe, $zerof,
+ $x, $y,
+ "constant", 0,
+ [ 0, 1, 2 ], [ 0, 1, 2 ],
+ [ 0, 1, 2 ], [ 0, 1, 2 ]
+ );
+
+ Context()->normalStrings;
+ $leftLastStep = Formula("($asided*$eliminateYscalar1+$csided*$eliminateYscalar2)*$x")->reduce->reduce;
+ $rightLastStep = Formula("$esided*$eliminateYscalar1+$fsided*$eliminateYscalar2")->reduce->reduce;
+ Context()->texStrings;
+
+ $Attack = $Attack
+ . " Since no coefficients are [`1`] or [`-1`], we choose to use the method of elimination, also known as the addition method. The first step is to algebraically rearrange both equations into the standard form with all variables aligned on the left and constants on the right.
+
+ [```\\left\\{\\begin{aligned}
+ $left1 & = $right1\\\\
+ $left2 & = $right2
+ \\end{aligned}\\right.```]
+
+Then we identify a common multiple of the [`$y`] coefficients. In this case, a common multiple is [`$lcmy`]. We rescale each equation so that the coefficients of [`$y`] become [`$lcmy`] and [`-$lcmy`].
+
+ [```\\left\\{\\begin{aligned}
+ $left1Step & = $right1Step\\\\
+ $left2Step & = $right2Step
+ \\end{aligned}\\right.```]
+
+
+ [```\\left\\{\\begin{aligned}
+ $left1NextStep & = $right1NextStep\\\\
+ $left2NextStep & = $right2NextStep
+ \\end{aligned}\\right.```]
+
+And now if we add left sides and right sides:
+
+ [```\\begin{aligned}
+ $leftLastStep & = $rightLastStep\\\\
+ \\end{aligned}```]
+
+";
+
+ if ($determinant == Fraction(0, 1) and ($leftLastStep != $rightLastStep)) {
+ $Attack = $Attack
+ . "Since this is a false equation no matter what [`$x`] and [`$y`] are, the system has no solutions.";
+ } elsif ($determinant == Fraction(0, 1) and ($leftLastStep == $rightLastStep)) {
+ $Attack = $Attack
+ . "Since this equation is true no matter what [`$x`] and [`$y`] are, the system has infinitely solutions; Just let [`$x`] take any value and then use either equation to solve for [`$y`]. The pair that you get from this will satisfy both equations.";
+ } else {
+ $left1Step = ($eliminateXscalar1 == 1) ? $left1 : "$eliminateXscalar1\\left($left1\\right)";
+ $right1Step = ($eliminateXscalar1 == 1) ? $right1 : "$eliminateXscalar1\\left($right1\\right)";
+ $left2Step = ($eliminateXscalar2 == 1) ? $left2 : "$eliminateXscalar2\\left($left2\\right)";
+ $right2Step = ($eliminateXscalar2 == 1) ? $right2 : "$eliminateXscalar2\\left($right2\\right)";
+
+ (
+ $left1NextStep, $right1NextStep, $left2NextStep, $right2NextStep, $leftOrd1, $rightOrd1, $leftOrd2,
+ $rightOrd2
+ )
+ = makeSides(
+ $asided * $eliminateXscalar1, $bsided * $eliminateXscalar1,
+ $csided * $eliminateXscalar2, $dsided * $eliminateXscalar2,
+ $esided * $eliminateXscalar1, $fsided * $eliminateXscalar2,
+ $xside1, $yside1,
+ $cside1, $xside2,
+ $yside2, $cside2,
+ $zeroa, $zerob,
+ $zeroc, $zerod,
+ $zeroe, $zerof,
+ $x, $y,
+ "constant", 0,
+ [ 0, 1, 2 ], [ 0, 1, 2 ],
+ [ 0, 1, 2 ], [ 0, 1, 2 ]
+ );
+
+ Context()->normalStrings;
+ $leftLastStep = Formula("($bsided*$eliminateXscalar1+$dsided*$eliminateXscalar2)*$y")->reduce->reduce;
+ $rightLastStep = Formula("$esided*$eliminateXscalar1+$fsided*$eliminateXscalar2")->reduce->reduce;
+ Context()->texStrings;
+
+ $Attack = $Attack
+ . "So [`$x = $xsol`]. Now we go back to an earlier stage and take the same steps to eliminate [`$x`].
+
+ [```\\left\\{\\begin{aligned}
+ $left1Step & = $right1Step\\\\
+ $left2Step & = $right2Step
+ \\end{aligned}\\right.```]
+
+
+ [```\\left\\{\\begin{aligned}
+ $left1NextStep & = $right1NextStep\\\\
+ $left2NextStep & = $right2NextStep
+ \\end{aligned}\\right.```]
+
+And now if we add left sides and right sides:
+
+ [```\\begin{aligned}
+ $leftLastStep & = $rightLastStep\\\\
+ \\end{aligned}```]
+
+So [`$y=$ysol`]. So the solution to the system is [`$x=$xsol, $y=$ysol`].
+
+";
+ }
+ }
+ } #end of else that begins after 0 coefficients are ruled out
+
+ Context()->normalStrings;
+}
+
+#
+# Set up the LinearSystems context
+#
+sub _SystemsOfLinearEquationsProblemPCC_init {
+ my $context = $main::context{LinearSystems} = Parser::Context->getCopy("LimitedFraction");
+
+ $context->flags->set(
+ reduceFractions => 0,
+ showMixedNumbers => 0,
+ showExtraParens => 0
+ );
+ $context->parens->redefine('{');
+ $context->operators->redefine(',', using => ',', from => 'Numeric');
+ $context->operators->redefine('and', using => ',', from => 'Numeric');
+ $context->operators->set(
+ ',' => { string => ' and ', TeX => '\hbox{ and }' },
+ 'and' => { string => ' and ', TeX => '\hbox{ and }' }
+ );
+ $context->lists->set(List => { separator => " and " });
+
+ $context->strings->add(
+ "no solutions" => {},
+ "no solution" => { alias => 'no solutions' },
+ "none" => { alias => 'no solutions' },
+ "infinitely many solutions" => {},
+ "infinitely many" => { alias => 'infinitely many solutions' },
+ "infinite" => { alias => 'infinitely many solutions' },
+ );
+
+ $context->lists->set(Point => { open => "(", close => ")" });
+ $context->parens->set("(" => { type => "Point", close => ")" });
+
+}
+
+sub solutionChecker {
+ my ($correct, $student, $ans, $nth, $value) = @_;
+ if ($correct->type eq "Assignment") {
+ my ($svar, $sfrac) = $student->value; # get the variable and fraction
+ #return 0 unless Value::classMatch($sfrac,'Fraction') && Fraction($sfrac)->isReduced;
+ if (Value::classMatch($sfrac, 'Fraction')) {
+ return 0 unless $sfrac->isReduced;
+ }
+ }
+ return $correct == $student;
+}
+
+sub extraChecker {
+ my ($student, $ansHash, $nth, $value) = @_;
+ if ($student eq "no solutions") {
+ $student->context->setError("This system of equations does have a solution",
+ "", undef, undef, $Value::CMP_WARNING)
+ unless $ans->{isPreview};
+ return;
+ }
+ if ($student eq "infinitely many solutions") {
+ $student->context->setError("This system of equations has exactly one solution",
+ "", undef, undef, $Value::CMP_WARNING)
+ unless $ans->{isPreview};
+ return;
+ }
+ if ($student eq "infinity") {
+ $student->context->setError(
+ "Infinity is a term for the concept of a *single* arbitrarily large number. Do you mean to say 'infinitely many solutions'?",
+ "", undef, undef, $Value::CMP_WARNING
+ ) unless $ans->{isPreview};
+ return;
+ }
+ if (($student->type ne "Assignment" && $ansHash->{student_formula}->type ne "Assignment")) {
+ if (($x ne "x") or ($y ne "y") or ($student->type ne "Point" && $ansHash->{student_formula}->type ne "Point")) {
+ $student->context->setError("Each part of your solution should be written in the form variable = value",
+ "", undef, undef, $Value::CMP_WARNING)
+ unless $ans->{isPreview};
+ return;
+ } else {
+ $student->context->setError("This is a trigger to check in Point context",
+ "", undef, undef, $Value::CMP_WARNING)
+ unless $ans->{isPreview};
+ return;
+ }
+ }
+ my ($svar, $sfrac) = $student->value; # get the variable and fraction
+ if (Value::classMatch($sfrac, 'Fraction') && !$sfrac->isReduced) {
+ $student->context->setError("Your $nth $value is not reduced", "", undef, undef, $Value::CMP_WARNING)
+ unless $ans->{isPreview};
+ return;
+ }
+ return Value::Real->typeMatch($student);
+}
+
+sub postFilterForPoints {
+ my $ansHash = shift;
+ if (!($ansHash->{score}) and ($ansHash->{ans_message} eq "This is a trigger to check in Point context")) {
+ my $studentString = $ansHash->{original_student_ans};
+ my $check = $pointAns->cmp()->evaluate($studentString);
+ $ansHash = $check;
+ if ($check->{score} == 1) {
+ my ($studx, $study) = $ansHash->{student_value}->value;
+ $ansHash->{score} = $check->{score};
+ $ansHash->{ans_message} = $check->{ans_message};
+ $ansHash->{correct_ans_latex_string} = $pointAns->TeX;
+ if (Value::classMatch($study, 'Fraction')) {
+ if (!($study->isReduced) or ($study->value)[1] == 1) {
+ $ansHash->{score} = 0;
+ $ansHash->{ans_message} = "Your second entry is not reduced";
+ }
+ }
+ if (Value::classMatch($studx, 'Fraction')) {
+ if (!($studx->isReduced) or ($studx->value)[1] == 1) {
+ $ansHash->{score} = 0;
+ $ansHash->{ans_message} = "Your first entry is not reduced";
+ }
+ }
+ }
+ }
+ return $ansHash;
+}
+
+1;
diff --git a/macros/math/VectorListCheckers.pl b/macros/math/VectorListCheckers.pl
new file mode 100644
index 0000000000..86afb1a013
--- /dev/null
+++ b/macros/math/VectorListCheckers.pl
@@ -0,0 +1,280 @@
+sub _VectorListCheckers_init { }; # don't reload this file
+
+=pod
+
+=head1 NAME
+
+VectorListCheckers.pl
+
+=head1 SYNOPSIS
+
+Provides subroutines for answer checking lists MathObjects
+vectors with real entries.
+
+=head1 DESCRIPTION
+
+First, load the C macro file.
+
+=over 12
+
+=item C
+
+= back
+
+For a MathObject list of MathObject vectors, the way to use the
+answer checkers is the same as using a custom answer checker
+inside of C~~&name_of_answer_checker_subroutine)>
+such as
+
+=over 12
+
+=item Ccmp( checker=~~&basis_checker_list_of_vectors ) );>
+
+=item C + s * <0,1,0> + t * <0,0,1>")-cmp( checker=~~&affine_subspace_checker_vectors ) );>
+
+=back
+
+The "list of vectors" at the end of the checker name refers to the
+fact that the student answer is a list of vectors.
+
+Here is an example of how to use these answer checkers.
+
+=over 12
+
+DOCUMENT();
+loadMacros(
+"PGstandard.pl",
+"MathObjects.pl",
+"VectorListCheckers.pl",
+"PGcourse.pl",
+);
+$showPartialCorrectAnswers = 1;
+TEXT(beginproblem());
+
+Context('Vector');
+
+$B = Matrix([[1,0,0],[0,1,0],[0,0,0]]);
+
+$answer = List(ColumnVector(1,0,0),ColumnVector(0,1,0));
+
+Context()->texStrings;
+BEGIN_TEXT
+A basis for the column space of \( B = $B \) is
+$BR
+$BR
+\{ $answer->ans_rule(60) \}
+$BR
+$BR
+Enter your answer as a comma separated list of vectors, such as
+\( \verb+<1,2,3>,<4,5,6>+ \).
+END_TEXT
+Context()->normalStrings;
+
+ANS( $answer->cmp(list_checker=>~~&basis_checker_list_of_vectors) );
+
+
+
+ENDDOCUMENT();
+
+=back
+
+The answer evaluation section of the PG file is totally standard.
+
+=over 12
+
+ANS( $multians->cmp );
+
+=back
+
+The C should be used for
+solutions to non-homogeneous systems of linear equations for
+which the solution is essentially a point plus the span of
+several linearly independent vectors. When using the parametric
+plane checker, the first vector input always serves as a point
+on the hyperplane (i.e., the first vector input is always a
+particular solution), while the remaining vectors are a basis for
+the hyperplane (i.e., they span the homogeneous solution set).
+
+=head1 AUTHORS
+
+Paul Pearson, Hope College, Department of Mathematics
+
+=cut
+
+################################################
+
+loadMacros("MathObjects.pl",);
+
+sub basis_checker_list_of_vectors {
+
+ my ($correct, $student, $ansHash, $value) = @_;
+ my @c = @{$correct};
+ my @s = @{$student};
+ my $nc = scalar(@c);
+ my $ns = scalar(@s);
+ my $score = 0;
+ my @errors = ();
+
+ return ($score, @errors) if $nc != $ns;
+
+ if ($nc == 1) {
+
+ if (Vector($s[0])->isParallel($c[0])) { return ($nc, @errors); }
+
+ } else {
+
+ # Most of the answer checking is done on integers
+ # or on decimals like 0.24381729, so we will set the
+ # tolerance accordingly in a local context.
+ my $context = Context()->copy;
+ $context->flags->set(
+ tolerance => 0.001,
+ tolType => "absolute",
+ );
+
+ # put the correct vectors into the columns of a matrix $C
+ my @cor = ();
+ foreach my $i (0 .. $nc - 1) {
+ push(@cor, Matrix($c[$i]));
+ }
+ my $C = Matrix(@cor)->transpose;
+
+ # put the student vectors into the columns of a matrix $S
+ my @stu = ();
+ foreach my $i (0 .. $ns - 1) {
+ push(@stu, Matrix($s[$i]));
+ }
+ my $S = Matrix(@stu)->transpose;
+
+ # Put $C and $S into the local context so that
+ # all of the computations that follow will also be in
+ # the local context.
+ $C = Matrix($context, $C);
+ $S = Matrix($context, $S);
+
+ # Theorem: A^T A is invertible if and only if A has linearly independent columns.
+
+ # Check that the professor's vectors are, in fact, linearly independent.
+ $CTC = ($C->transpose) * $C;
+ warn "Correct answer is a linearly dependent set." if ($CTC->det == 0);
+
+ # Check that the student's vectors are linearly independent
+ if ((($S->transpose) * $S)->det == 0) {
+ Value->Error("Your vectors are linearly dependent");
+ return 0;
+ }
+
+ # S = student, C = correct, X = change of basis matrix
+ # Solve S = CX for X using (C^T C)^{-1} C^T S = X.
+ $X = ($CTC->inverse) * (($C->transpose) * $S);
+ if ($S == $C * $X) { $score = $nc; }
+
+ return ($score, @errors);
+
+ }
+
+}
+
+########################################################
+
+$vector_list_column_syntax_angle_brackets = MODES(
+ TeX => '',
+ HTML => "Enter a column vector such as
+\\( \\left\\lbrack \\begin{array}{r} 1 \\\\ 2 \\end{array} \\right\\rbrack \\)
+using the syntax \\( \\verb+<1,2>+ \\).
+If there is more than one vector in your answer, enter
+your answer as a comma separated list of vectors, such as
+\\( \\verb+<1,2>,<3,4>+ \\)."
+);
+
+########################################################
+
+$vector_list_row_syntax_angle_brackets = MODES(
+ TeX => '',
+ HTML => "Enter a row vector using the syntax \\( \\verb+<1,2>+ \\).
+If there is more than one vector in your answer, enter
+your answer as a comma separated list of vectors, such as
+\\( \\verb+<1,2>,<3,4>+ \\)."
+);
+
+########################################################
+
+sub affine_subspace_checker_vectors {
+
+ my ($correct, $student, $ansHash) = @_;
+ my @s = ();
+ my @c = ();
+
+ # Most of the answer checking is done on integers
+ # or on decimals like 0.24381729, so we will set the
+ # tolerance accordingly in a local context.
+ my $context = Context()->copy;
+ $context->flags->set(
+ tolerance => 0.001,
+ tolType => "absolute",
+ );
+
+ # Get the variables from the context
+ my @vars = $context->variables->names;
+
+ # Make an array of zeros the same length as @vars
+ my @zeros = ();
+ foreach (@vars) { push(@zeros, 0); }
+
+ # Evaluate the correct and student answers on the zero vector
+ my %h;
+ @h{@vars} = @zeros;
+ push(@c, Matrix($context, $correct->eval(%h)));
+ push(@s, Matrix($context, $student->eval(%h)));
+
+ # Make standard basis vectors e_i with ith entry = 1 and all other entries = 0
+ # and evaluate the correct and student answers on e_i
+ my @temp = ();
+ foreach my $i (0 .. $#vars) {
+ @temp = @zeros;
+ $temp[$i] = 1;
+ @h{@vars} = @temp;
+ my $c_temp = Matrix($context, $correct->eval(%h));
+ my $s_temp = Matrix($context, $student->eval(%h));
+ $c_temp = $c_temp - $c[0];
+ $s_temp = $s_temp - $s[0];
+ push(@c, $c_temp);
+ push(@s, $s_temp);
+ }
+
+ # Put the results into the columns of matrices
+ my $C0 = Matrix($context, shift(@c))->transpose; # column vector = displacement vector
+ my $C = Matrix($context, @c)->transpose; # matrix columns = vectors that span the hyperplane
+ my $S0 = Matrix($context, shift(@s))->transpose; # column vector = displacement vector
+ my $S = Matrix($context, @s)->transpose; # matrix columns = vectors that span the hyperplane
+
+ # Theorem: A^T A is invertible if and only if A has linearly independent columns.
+
+ # Check that the professor's vectors are, in fact, linearly independent.
+ $CTC = ($C->transpose) * $C;
+ warn "Correct answer is a linearly dependent set." if ($CTC->det == 0);
+
+ # Check that the student's vectors are linearly independent
+ if ((($S->transpose) * $S)->det == 0) {
+ Value->Error("Your vectors do not span a (hyper) plane of the correct dimension");
+ return 0;
+ }
+
+ # solve (S_0 = C_0 + C A) for the column vector A of weights using
+ # (S_0 - C_0) = C A
+ # C^T (S_0 - C_0) = C^T C A
+ # (C^T C)^{-1} C^T (S_0 - C_0) = A
+ my $A = ($CTC->inverse) * ($C->transpose) * ($S0 - $C0);
+ if ($S0 != $C0 + $C * $A) {
+ #Value->Error("Your particular solution $S0 is incorrect");
+ return 0;
+ }
+
+ # S = student, C = correct, X = change of basis matrix
+ # Solve S = CX for X using (C^T C)^{-1} C^T S = X.
+ $X = ($CTC->inverse) * (($C->transpose) * $S);
+ return $S == $C * $X;
+
+}
+
+1;
diff --git a/macros/math/algebraMacros.pl b/macros/math/algebraMacros.pl
new file mode 100644
index 0000000000..c458626f37
--- /dev/null
+++ b/macros/math/algebraMacros.pl
@@ -0,0 +1,250 @@
+# algebraMacros.pl
+
+# define any custom subroutines you want and then use them in
+# problems by including the file in loadMacros() calls.
+
+sub _init_macro_template {
+
+ #Possibly add initialization code here
+ #sub routine is not required, but prevents the macro from being re-loaded
+
+}
+
+######################## subroutines
+
+# fisher-yates shuffle
+# argument: reference to a list
+# shuffles list in-place
+# returns: nothing
+sub fyshuffle {
+ my $array = shift;
+ my $i = @$array;
+ while (--$i) {
+ my $j = random(0, $i, 1);
+ @$array[ $i, $j ] = @$array[ $j, $i ];
+ }
+}
+
+# extended euclidean algorithm
+# by Dick Lane
+# http://webwork.maa.org/moodle/mod/forum/discuss.php?d=2286
+# arguments: a, b
+# returns: d, x, y, s, t where gcd( a, b ) = d = a( x + sk ) + b( y + tk ) for all k
+sub xgcd ($$) {
+ my ($a, $b, $x, $y, $s, $t) = (@_, 1, 0, 0, 1);
+
+ while ($b) {
+ my $q = int($a / $b);
+ ($a, $x, $y, $b, $s, $t) = ($b, $s, $t, $a - $q * $b, $x - $q * $s, $y - $q * $t);
+ }
+
+ return [ $a, $x, $y, $s, $t ];
+}
+
+# subroutine to decompose a permutation into a product of disjoint cycles.
+# argument: reference to a list containing the permutation data
+# that is, element 0 of the list is f(0), element 1 is f(1), etc
+# returns: mathObject list of lists representing the cycle decomposition of the permutation
+sub disjointCycles {
+ $p = shift;
+
+ # create a hash representing the permutation
+ %pHash = ();
+ $pHash{$_} = $$p[$_] foreach (0 .. -1 + scalar @$p);
+
+ @cycles = ();
+ while (keys %pHash) {
+ # start with first available key
+ @c = (keys %pHash)[0];
+
+ # keep looping through set, adding elements to the current cycle (c)
+ # until we get back to the beginning of c
+ push @c, delete $pHash{ $c[-1] } while $pHash{ $c[-1] } != $c[0];
+
+ delete $pHash{ $c[-1] };
+
+ # add c to cycles if it's not just a 1-cycle
+ push @cycles, [ map { $_ + 1 } @c ] if @c > 1;
+ }
+
+ # making the lists of lists must be done carefully in order to get the behavior we want
+ # we want the cycles (Lists within the List) to always have parentheses, even if there's
+ # only one. But we want the outer List to never have parentheses
+ if (scalar @cycles > 1) {
+ $result = List(map { List(join(',', @$_)) } (@cycles));
+ } elsif (scalar @cycles > 0) {
+ $result = List(map { List('(' . join(',', @$_) . ')') } @cycles);
+ } else {
+ Context()->strings->add(id => { alias => "identity" });
+ $result = List('(' . $cycle[0] . ')');
+ }
+ return $result;
+}
+
+# subroutine to decompose a permutation into a product of transpositions ( 2-cycles ).
+# argument: mathObject list of lists representing the cycle decomposition of the permutation (i.e., the output of disjointCycles() )
+# returns: mathObject list of lists representing the permutation as a list of transpositions
+sub cyclesToTranspositions {
+ my $listOfCycles = shift;
+
+ @transpositions = ();
+
+ # decompose each cycle ( a_1 a_2 ... a_n ) into ( a_1 a_2 )( a_1 a_3 ) ... ( a_1 a_n )
+ foreach ($listOfCycles->value) {
+ my @cycle = $_->value;
+ foreach (1 .. @cycle - 1) {
+ push @transpositions, List(@cycle[ 0, $_ ]);
+ }
+ }
+
+# unlike disjointCycles, this subroutine is only used in a problem where the permutation has order > 4, so there will always be more than one element. Therefore we don't have to do any weird stuff to format the list (make parentheses display correctly)
+ return List(@transpositions);
+}
+
+# subroutine to determine the order of a permutation
+# argument: a list of lists representing a permutation in the form of a product of disjoint cycles ( i.e., the output of disjointCycles() )
+# returns: mathObject integer representing the order of the permutation
+sub cycleOrder {
+ my $p = shift; # list of lists
+
+ my $order = 1;
+ $order = lcm($order, $_->length) foreach $p->value;
+
+ return Compute($order);
+}
+
+# subroutine to determine the parity of a permutation
+# argument: a list of lists representing a permutation in the form of a product of disjoint cycles (i.e., the output of disjointCycles() )
+# returns: TRUE if the permutation is even, FALSE if the permutation is odd
+sub isEven {
+ my $p = shift;
+
+ my $numberEvenCycles = scalar grep { $_->length % 2 == 0 } $p->value;
+
+ return !($numberEvenCycles % 2);
+
+}
+
+######################## frequently-used custom checkers
+
+# you can call these when checking answers like this:
+#
+# ANS( $f->cmp( checker => ~~&modChecker ) );
+
+# make sure the external variables are available; for instance, in order to use
+# the modChecker subroutine, you must have the variable $modulus defined in
+# your problem
+
+###############################################################################
+# modChecker
+###############################################################################
+# Type: checker
+# Used in: RingsDefinition3.pg
+# RingsDefinition4.pg
+# RingsDefinition5.pg
+# RingsDefinition7.pg
+# RingsQuotientPolynomial3.pg
+# RingsQuotientPolynomial4.pg
+# RingsIdealsHomomorphisms2.pg
+# Purpose: Compare the student's answer (a number) to the correct
+# answer (a number), modulo $modulus
+# External variables:
+# $modulus
+# Comment: This checker is defined in algebraMacros.pl because it's used
+# in so many problems
+###############################################################################
+sub modChecker {
+ my ($correct, $student, $ansHash) = @_; # get correct and student MathObjects
+ return ($student % $modulus == $correct % $modulus ? 1 : 0);
+}
+###############################################################################
+
+###############################################################################
+# checkCycles
+###############################################################################
+# Type: checker
+# Used in: Permutations1.pg
+# Permutations2.pg
+# Permutations5.pg
+# Permutations6.pg
+# Purpose: Compare a student's cycle to a correct cycle, see if they
+# represent the same permutation (i.e. ( 1 2 3 ) = ( 2 3 1 ) )
+# External variables:
+# Comment: This checker is defined in algebraMacros.pl because it's used
+# in so many problems
+###############################################################################
+sub checkCycles {
+ my ($correct, $student, $ansHash) = @_;
+
+ @studentCycle = $student->value;
+ @correctCycle = $correct->value;
+
+ # immediately fail if the cycles are not the same length
+ return 0 if @studentCycle != @correctCycle;
+
+ # try each offset, see if the lists are the same
+ foreach (0 .. @studentCycle - 1) {
+ my $i = 0;
+ $i++ while $i < @studentCycle && $studentCycle[$i] == $correctCycle[ -$_ + $i ];
+ return 1 if $i == @studentCycle;
+ }
+
+ return 0;
+}
+###############################################################################
+
+###############################################################################
+# checkListOfTranspositions
+###############################################################################
+# Type: checker
+# Used in: Permutations4.pg
+# Permutations8.pg
+# Purpose: Compare a student's sequence of transpositions to a correct
+# sequence of transpositions, see if they represent the same
+# permutation
+# External variables:
+# @x - list of elements in the set on which permutations are
+# defined
+# Comment: This checker is defined in algebraMacros.pl because it's used
+# in so many problems
+###############################################################################
+sub checkListOfTranspositions {
+ my ($correct, $student, $ansHash, $value) = @_;
+
+ # create hashes which will be used to store the student and correct permutations
+ my (%studentPerm, %correctPerm);
+ $studentPerm{$_} = $correctPerm{$_} = $_ foreach (1 .. @x);
+
+ # apply each of the correct transpositions in succession, from right to left
+ foreach (1 .. @$correct) {
+ # read the transposition ( a, b ) as a pair of integers
+ $a = ($correct->[ -$_ ]->value)[0]->value;
+ $b = ($correct->[ -$_ ]->value)[1]->value;
+
+ # switch a and b elements of the hash
+ @correctPerm{ $a, $b } = @correctPerm{ $b, $a };
+ }
+
+ # apply each of the student's transpositions in succession, from right to left
+ foreach (1 .. @$student) {
+ # check to make sure this entry is a list of 2 elements, both of which are positive integers in the set @x
+ return (0, "one of your entries is not a well-defined transposition on this set")
+ if $student->[ -$_ ]->length != 2
+ || scalar grep { !Value::isReal($_) || $_ != int $_ || $_ < 1 || $_ > @x } $student->[ -$_ ]->value;
+
+ # read the transposition ( a, b ) as a pair of integers
+ $a = ($student->[ -$_ ]->value)[0]->value;
+ $b = ($student->[ -$_ ]->value)[1]->value;
+
+ # switch elements a and b of the hash
+ @studentPerm{ $a, $b } = @studentPerm{ $b, $a };
+ }
+
+ # read the two hashes to make sure they're the same. if not, the answer is wrong
+ return scalar(grep { $studentPerm{$_} != $correctPerm{$_} } (1 .. @x)) ? (0) : (scalar(@$student));
+
+# an alternative way to do this would be to compute the permutation represented by the correct list of transpositions, then do the inverse of the student's list of transpositions (work left-to-right instead of right-to-left) on the same list, and see if it results in the identity.
+}
+###############################################################################
+
+1; #required at end of file - a perl thing
diff --git a/macros/bizarroArithmetic.pl b/macros/math/bizarroArithmetic.pl
similarity index 100%
rename from macros/bizarroArithmetic.pl
rename to macros/math/bizarroArithmetic.pl
diff --git a/macros/customizeLaTeX.pl b/macros/math/customizeLaTeX.pl
similarity index 100%
rename from macros/customizeLaTeX.pl
rename to macros/math/customizeLaTeX.pl
diff --git a/macros/draggableProof.pl b/macros/math/draggableProof.pl
similarity index 100%
rename from macros/draggableProof.pl
rename to macros/math/draggableProof.pl
diff --git a/macros/draggableSubsets.pl b/macros/math/draggableSubsets.pl
similarity index 100%
rename from macros/draggableSubsets.pl
rename to macros/math/draggableSubsets.pl
diff --git a/macros/math/fixedPrecision.pl b/macros/math/fixedPrecision.pl
new file mode 100644
index 0000000000..37d5abddf3
--- /dev/null
+++ b/macros/math/fixedPrecision.pl
@@ -0,0 +1,59 @@
+sub _fixedPrecision_init { }
+loadMacros('MathObjects.pl');
+
+package FixedPrecision;
+our @ISA = ("Value::Real");
+
+sub new {
+ my $self = shift;
+ my $class = ref($self) || $self;
+ my $context = (Value::isContext($_[0]) ? shift : $self->context);
+ my $x = shift;
+ my $n = shift;
+ Value::Error("Too many arguments") if scalar(@_) > 0;
+ if (defined($n)) {
+ $x = main::prfmt($x, "%.${n}f");
+ } else {
+ $x =~ s/\s+//g;
+ my ($int, $dec) = split(/\./, $x);
+ $n = length($dec);
+ }
+ $self = bless $self->SUPER::new($context, $x), $class;
+ $self->{decimals} = $n;
+ $self->{isValue} = 1;
+ return $self;
+}
+
+sub string {
+ my $self = shift;
+ main::prfmt($self->value, "%." . $self->{decimals} . "f");
+}
+
+sub compare {
+ my ($self, $l, $r) = Value::checkOpOrder(@_);
+ $l cmp $r;
+}
+
+package FixedPrecisionNumber;
+our @ISA = ("Parser::Number");
+
+sub new {
+ my $self = shift;
+ my $class = ref($self) || $self;
+ my $equation = shift;
+ my $context = $equation->{context};
+ $self = bless $self->SUPER::new($equation, @_), $class;
+ $self->{value} = FixedPrecision->new($self->{value_string});
+ return $self;
+}
+
+sub string { (shift)->{value}->string(@_) }
+sub TeX { (shift)->{value}->TeX(@_) }
+
+package main;
+
+Context()->{parser}{Number} = "FixedPrecisionNumber";
+
+sub FixedPrecision { FixedPrecision->new(@_) }
+
+1;
diff --git a/macros/math/interpMacros.pl b/macros/math/interpMacros.pl
new file mode 100644
index 0000000000..3232a6f298
--- /dev/null
+++ b/macros/math/interpMacros.pl
@@ -0,0 +1,33 @@
+# interpMacros.pl
+# Rename this file (with .pl extension) and place it in your course macros directory,
+
+sub interpVals {
+ $arrayLength = ($#_) / 2;
+ @A_ARRAY = @_[ 0 .. ($arrayLength - 1) ];
+ @B_ARRAY = @_[ $arrayLength .. ($#_ - 1) ];
+ $A_VAL = $_[$#_];
+ $arrayLength2 = $#A_ARRAY;
+
+ for ($i = 1; $i < ($#A_ARRAY + 1); $i++) {
+ if ($A_VAL == $A_ARRAY[ $i - 1 ]) {
+ $B_VAL = $B_ARRAY[ $i - 1 ];
+ last;
+ } elsif ($A_ARRAY[ $i - 1 ] < $A_VAL && $A_VAL < $A_ARRAY[$i]) {
+ $AL = $A_ARRAY[ $i - 1 ];
+ $AR = $A_ARRAY[$i];
+ $BL = $B_ARRAY[ $i - 1 ];
+ $BR = $B_ARRAY[$i];
+ $B_VAL = (($A_VAL - $AL) / ($AR - $AL) * ($BR - $BL)) + $BL;
+ last;
+ } elsif ($A_VAL == $A_ARRAY[$i]) {
+ $B_VAL = $B_ARRAY[$i];
+ last;
+ } else {
+ $B_VAL = $B_ARRAY[0];
+ }
+ }
+
+ return $B_VAL;
+}
+
+1; #required at end of file - a perl thing
diff --git a/macros/math/regrfnsPG.pl b/macros/math/regrfnsPG.pl
new file mode 100644
index 0000000000..71e1073120
--- /dev/null
+++ b/macros/math/regrfnsPG.pl
@@ -0,0 +1,109 @@
+
+# functions for simple linear regression in perl
+
+#/* "a","b" are the two endpoints of the range of
+# random integers to be produced. */
+# inputs $a,$b are integers
+#sub rndgen
+#{ local($a,$b,$r);
+# $a=$_[0]; $b=$_[1];
+# $r=rand($b-$a+1); # in (0,b-a+1)
+# #print $a," ",$b," ",$r,"\n";
+# $r=int($r)+$a; # between a,b inclusive
+# $r;
+#}
+
+# N (size of population) and ssize (sample size) are inputs
+sub isamp {
+ local ($N, $ssize, $N1, $j, $k, $temp, @x, @xsub);
+ $N = $_[0];
+ $ssize = $_[1];
+ $N1 = $N - 1;
+ @x = 0 .. $N1;
+ for ($j = 0; $j < $ssize; $j++) { #$k=rndgen($j,$N1);
+ $k = random($j, $N1, 1); # using function in WebWork
+ $temp = $x[$j];
+ $x[$j] = $x[$k];
+ $x[$k] = $temp;
+ }
+ #for($j=0; $j<$ssize; $j++) { $xsub[$j]=$x[$j]; }
+ @xsub = @x[ 0 .. ($ssize - 1) ];
+ @xsub;
+}
+
+# simple linear regression
+# input xvec, yvec : assumed as one vector here
+# output ($n,$xbar,$ybar,$sx2,$sy2,$sxy,$b0,$b1,$sse,$mse,$x0);
+sub lsreg {
+ local ($n, $i, $xi, $yi, $sx1, $sx2, $sy1, $sy2, $sxy, $xbar, $ybar, $b0, $b1, $sse, $mse);
+ $n = ($#_ + 1) / 2;
+ #print $n,"\n";
+ $sx1 = 0;
+ $sx2 = 0;
+ $sy1 = 0;
+ $sy2 = 0;
+ $sxy = 0;
+ for ($i = 0; $i < $n; $i++) { #$xi=$xx[$i]; $yi=$yy[$i];
+ $xi = $_[$i];
+ $yi = $_[ $i + $n ];
+ $sx1 = $sx1 + $xi;
+ $sx2 = $sx2 + $xi * $xi;
+ $sy1 = $sy1 + $yi;
+ $sy2 = $sy2 + $yi * $yi;
+ $sxy = $sxy + $xi * $yi;
+ }
+ $xbar = $sx1 / $n;
+ $ybar = $sy1 / $n;
+ $sx2 = ($sx2 - $n * $xbar**2) / ($n - 1);
+ $sy2 = ($sy2 - $n * $ybar**2) / ($n - 1);
+ $sxy = ($sxy - $n * $xbar * $ybar) / ($n - 1);
+ $b1 = $sxy / $sx2;
+ $b0 = $ybar - $b1 * $xbar;
+ $sse = ($sy2 - $sxy**2 / $sx2) * ($n - 1);
+ $mse = $sse / ($n - 2);
+ @out = ($n, $xbar, $ybar, $sx2, $sy2, $sxy, $b0, $b1, $sse, $mse);
+ @out;
+}
+
+# subpopulation mean at x0
+#sqrt(mse*(1/n+(x0-xbar)^2/((n-1)*sx2)))
+# input : output of lsreg, plus x0
+# or ($n,$xbar,$ybar,$sx2,$sy2,$sxy,$b0,$b1,$sse,$mse,$x0);
+# output: point estimate and SE
+sub subpopmeanint {
+ local ($n, $xbar, $sx2, $b0, $b1, $x0, $muygx, $semu, @out);
+ $n = $_[0];
+ $xbar = $_[1];
+ $sx2 = $_[3];
+ $b0 = $_[6];
+ $b1 = $_[7];
+ $mse = $_[9];
+ $x0 = $_[10];
+ $muygx = $b0 + $b1 * $x0;
+ $semu = sqrt($mse * (1 / $n + ($x0 - $xbar)**2 / (($n - 1) * $sx2)));
+ @out = ($muygx, $semu);
+ @out;
+}
+
+#prediction interval at x=x0
+#sqrt(mse*(1+1/n+(x0-xbar)^2/((n-1)*sx2)))
+# input : output of lsreg, plus x0
+# ($n,$xbar,$ybar,$sx2,$sy2,$sxy,$b0,$b1,$sse,$mse,$x0);
+# output: point estimate and SE
+sub predint {
+ local ($n, $xbar, $sx2, $b0, $b1, $x0, $muygx, $semu, @out);
+ $n = $_[0];
+ $xbar = $_[1];
+ $sx2 = $_[3];
+ $b0 = $_[6];
+ $b1 = $_[7];
+ $mse = $_[9];
+ $x0 = $_[10];
+ $muygx = $b0 + $b1 * $x0;
+ $semu = sqrt($mse * (1 + 1 / $n + ($x0 - $xbar)**2 / (($n - 1) * $sx2)));
+ @out = ($muygx, $semu);
+ @out;
+}
+
+1; #return true
+
diff --git a/macros/tableau.pl b/macros/math/tableau.pl
old mode 100755
new mode 100644
similarity index 100%
rename from macros/tableau.pl
rename to macros/math/tableau.pl
diff --git a/macros/tableau_main_subroutines.pl b/macros/math/tableau_main_subroutines.pl
similarity index 99%
rename from macros/tableau_main_subroutines.pl
rename to macros/math/tableau_main_subroutines.pl
index 85e4f4c7ae..67a027cba1 100644
--- a/macros/tableau_main_subroutines.pl
+++ b/macros/math/tableau_main_subroutines.pl
@@ -184,7 +184,5 @@ sub get_tableau_variable_values {
# and basis is {3,4,5} (start columns with 1)
# $n= 4; $m = 7
# $x1=0; $x2=0; $x3=s1=3; $x4=s2=6; $x5=s3=9; w=10=objective value
-#
-#
1;
diff --git a/macros/misc/PCCmacros.pl b/macros/misc/PCCmacros.pl
new file mode 100644
index 0000000000..7a4c82aa17
--- /dev/null
+++ b/macros/misc/PCCmacros.pl
@@ -0,0 +1,848 @@
+
+=head1 NAME
+
+PCCmacros.pl - A collection of tools found to be useful during the development of the PCC problem library.
+
+=head1 DESCRIPTION
+
+This file implements subroutines developed for use in problems originally developed for a PCC problem library
+
+To use it, load the macro file:
+
+ loadMacros(
+ "PGstandard.pl",
+ "MathObjects.pl",
+ "PCCmacros.pl",
+ );
+
+
+=cut
+
+###############################
+#Name: perlround
+#Input: a number to round, then a place to round to. e.g. 2=>hundredths, 0=>whole, -1=>tens
+#Output: $x rounded to the $n place. This attempts to overcome quirks with rounding when the cut part is like 0.005
+################################
+sub perlround {
+ # number to round
+ my $x = shift;
+ # place to round. e.g. 2=>hundredths, 0=>whole, -1=>tens
+ my $n = shift;
+ # "fix" non-integer input
+ $n = int($n);
+ # corresponding integer to round up or down
+ my $X = $x * 10**$n;
+ $X = ($X - sprintf("%.0f", $X) eq 0.5) ? int($X + 1) : sprintf("%.0f", $X);
+ return $X / 10**$n;
+}
+
+###############################
+#Name: RandomName
+#Input: None required
+#Optional Input: 'sex' => male or female
+#Output: A name that is randomly chosen from the list maintained here.
+# If 'sex' is specified, the name is from the list of that sex.
+################################
+sub RandomName {
+ my %options = @_;
+
+ my ($sex, $name, @femlist, @malelist);
+
+ $sex = $options{'sex'} if defined($options{'sex'});
+
+ $sex = 'both' unless defined($sex);
+
+ @femlist = (
+ 'Adrian', 'Alisa', 'Alyson', 'Amber', 'Annaly', 'Ashley', 'Barbara', 'Bobbi',
+ 'Briana', 'Candi', 'Carly', 'Carmen', 'Casandra', 'Charity', 'Charlotte', 'Cheryl',
+ 'Dawn', 'Diane', 'dMarie', 'Donna', 'Eileen', 'Elishua', 'Emily', 'Fabrienne',
+ 'Hannah', 'Haley', 'Heather', 'Holli', 'Irene', 'Izabelle', 'Janieve', 'Jenny',
+ 'Jessica', 'Julie', 'Kara', 'Kayla', 'Kandace', 'Katherine', 'Kim', 'Kristen',
+ 'Kylie', 'Laney', 'Laurie', 'Lesley', 'Lily', 'Lindsay', 'Lisa', 'Maria',
+ 'Martha', 'Maygen', 'Morah', 'Michele', 'Nenia', 'Nina', 'Olivia', 'Page',
+ 'Penelope', 'Perlia', 'Priscilla', 'Rebecca', 'Renee', 'Rita', 'Ronda', 'Samantha',
+ 'Sarah', 'Selena', 'Sharell', 'Sharnell', 'Sherial', 'Stephanie', 'Subin', 'Sydney',
+ 'Tammy', 'Teresa', 'Tiffany', 'Tracei', 'Virginia', 'Wendy'
+ );
+
+ @malelist = (
+ 'Aaron', 'Aleric', 'Alejandro', 'Andrew', 'Anthony', 'Benjamin', 'Blake', 'Brandon',
+ 'Brent', 'Carl', 'Chris', 'Cody', 'Connor', 'Corey', 'Daniel', 'Dave',
+ 'Dennis', 'Derick', 'Devon', 'Douglas', 'Emiliano', 'Eric', 'Evan', 'Farshad',
+ 'Gosheven', 'Grant', 'Gregory', 'Gustav', 'Hayden', 'Henry', 'Huynh', 'Ivan',
+ 'James', 'Jay', 'Jeff', 'Jerry', 'Jon', 'Joseph', 'Joshua', 'Ken',
+ 'Kenji', 'Kimball', 'Kurt', 'Marc', 'Matthew', 'Michael', 'Nathan', 'Neil',
+ 'Nicholas', 'Parnell', 'Peter', 'Phil', 'Randi', 'Ravi', 'Ross', 'Ryan',
+ 'Scot', 'Sean', 'Sebastian', 'Shane', 'Stephen', 'Thanh', 'Tien', 'Timothy',
+ 'Wenwu', 'Will'
+ );
+
+ if ($sex eq 'both') { $name = list_random(@femlist, @malelist); }
+ elsif ($sex eq 'female') { $name = list_random(@femlist); }
+ elsif ($sex eq 'male') { $name = list_random(@malelist); }
+
+ $name;
+}
+
+###############################
+#
+# Name: RandomVariableName
+#
+# Input: None required
+#
+# Optional Input: 'type' => variable, constant, integer, or function
+#
+# Output: A variable name like x, y, t, c, etc. that is randomly chosen from the list maintained here.
+# If 'type' is specified, the name is from the list of that type.
+#
+# Sample use:
+################################
+sub RandomVariableName {
+ my %options = @_;
+
+ my ($type, $name, @varlist, @constlist, @intlist, @functionlist);
+
+ $type = $options{'type'} if defined($options{'type'});
+
+ $type = 'all' unless defined($type);
+
+ @varlist = (
+ 'x',
+ 'y',
+ # 'z',
+ 'r',
+ # 's',
+ 't',
+ );
+
+ @constlist = ('a', 'b', 'c', 'A', 'B', 'C',);
+
+ @intlist = ( #'i', potential for context confusion with complex
+ #'j', and vector
+ #'k',
+ 'm',
+ 'n',
+ 'p',
+ 'q',
+ );
+
+ @functionlist = (
+ 'f',
+ 'g',
+ 'h',
+ #'k',
+ 'F',
+ 'G',
+ 'H',
+ 'K',
+ );
+
+ if ($type eq 'all') { $name = list_random(@varlist, @constlist, @intlist); }
+ elsif ($type eq 'variable') { $name = list_random(@varlist); }
+ elsif ($type eq 'constant') { $name = list_random(@constlist); }
+ elsif ($type eq 'integer') { $name = list_random(@intlist); }
+ elsif ($type eq 'function') { $name = list_random(@functionlist); }
+
+ $name;
+}
+
+###############################
+#
+# Name: xPower
+#
+# This is just an auxiliary subroutine for the polynomial macros that follow
+#
+################################
+
+sub xPower {
+ my ($power, $maxpower, $order) = @_;
+ if ($order eq 'ascending') { return $power; }
+ else { return ($maxpower - $power); }
+}
+
+###############################
+#
+# Name: PolyString
+#
+# Input: an arrays
+#
+# PolyString(~~@poly1)
+#
+# Arrays elements should be numerical or Math Objects. The idea is that
+# these are coefficients of a polynomial, either starting from the
+# constant term or from the leading term.
+#
+# Optional Input: order=>ascending or descending, defaults to descending
+# order is ignored if output=>array
+# Optional Input: var=> some string, defaulting to "x"
+#
+# Output: an polynomial string using var and ready to be fed to Compute
+#
+# For example, given
+#
+# @poly1 = (1,2); # represents x+2
+# @poly2 = (3,-1,0,4);
+#
+# PolyString(~~@poly1);
+#
+# 'x^1+2x^0'
+#
+# PolyString(~~@poly2,order=>ascending,var=>'z');
+#
+# '3x^0+-1x^1+0x^2+4x^3'
+#
+# Note that instance of +-, x^0, x^1, 0x^n, may be present,
+# but Math Objectification will handle that.
+#
+# Note: If you intend to feed the output string to Compute() or Formula(),
+# you should set the context reductions appropriately and apply ->reduce
+# *twice* to remove excess parentheses from negative coefficients.
+#
+################################
+
+sub PolyString {
+
+ my ($xref, %options) = @_;
+ my @local_x = @{$xref};
+
+ my ($order, $var, $exponentVar, $string);
+
+ if (defined($options{'order'})) {
+ $order = $options{'order'};
+ } else {
+ $order = 'descending';
+ }
+
+ if (defined($options{'var'})) {
+ $var = $options{'var'};
+ } else {
+ $var = 'x';
+ }
+
+ # new, cmh 6/18/13
+ if (defined($options{'exponentVar'})) {
+ $exponentVar = $options{'exponentVar'};
+ } else {
+ $exponentVar = '';
+ }
+
+ # original 6/18/13
+ # $string = "$local_x[0]".$var.'^'.xPower(0,$#local_x,$order);
+ # for my $i (1..$#local_x) {
+ # $string = $string.'+'."$local_x[$i]".$var.'^'.xPower($i,$#local_x,$order);
+ # }
+
+ # modified, cmh 6/18/13
+ $string = "$local_x[0]" . $var . '^{' . xPower(0, $#local_x, $order) . $exponentVar . '}';
+ for my $i (1 .. $#local_x) {
+ $string = $string . '+' . "$local_x[$i]" . $var . '^{' . xPower($i, $#local_x, $order) . $exponentVar . '}';
+ }
+ return $string;
+}
+
+###############################
+#
+# Name: PolyMult
+#
+# Input: two arrays, for example
+#
+# PolyMult(~~@poly1, ~~@poly2)
+#
+# Arrays elements should be numerical or Math Objects
+# where multiplication and addition are allowed. The idea is that
+# these are coefficients of a polynomial, either starting from the
+# constant term or from the leading term.
+#
+# Optional Input: order=>ascending or descending, defaults to descending
+# order is ignored if output=>array
+# Optional Input: var=> some string, defaulting to "x"
+# Optional Input: output=>array, simplified, unsimplified
+# which is either an array of coefficients, an expanded and simplified
+# polynomial string using var and ready to be fed to Compute, or the same
+# but unsimplified. Default is array.
+# cmh, 6/18/13
+# Optional Input: exponentVar=> some string, defaulting to ''
+# to be used if the polynomial looks like, for example
+# x^{3n}+x^{2n}+x^{n}
+#
+# Output: described in Optional Input
+#
+# For example, given
+#
+# @poly1 = (1,2); # represents x+2
+# @poly2 = (3,-1,4); # represents 3x^2-x+4
+#
+# PolyMult(~~@poly1, ~~@poly2,
+# order=>ascending, var=>'z', output=>unsimplified)
+#
+# '3z^3+-1z^2+4z+6z^2+-2z^1+8z^0'
+#
+# Note that instance of +- may be present, but Math Objectification
+# will handle that.
+#
+#
+# Given
+#
+# @poly1 = (Fraction(1,3),2); # represents 1/3x+2
+# @poly2 = (Fraction(1,2),Fraction(-1,3)); # represents 1/2x-1/3
+#
+# PolyMult(~~@poly1, ~~@poly2)
+#
+# (Frac(1,6),Frac(8,9),Frac(-1,9))
+#
+#
+# Note: If you intend to feed the output string to Compute() or Formula(),
+# you should set the context reductions appropriately and apply ->reduce
+# *twice* to remove excess parentheses from negative coefficients.
+#
+################################
+
+sub PolyMult {
+ my ($xref, $yref, %options) = @_;
+ my @local_x = @{$xref};
+ my @local_y = @{$yref};
+
+ my ($order, $var, $output, @productArray, $productString);
+
+ $order = $options{'order'} if defined($options{'order'});
+ $order = 'descending' unless defined($options{'order'});
+
+ $var = $options{'var'} if defined($options{'var'});
+ $var = 'x' unless defined($options{'var'});
+
+ $output = $options{'output'} if defined($options{'output'});
+ $output = 'array' unless defined($options{'output'});
+
+ # cmh, 6/18/13
+ $exponentVar = $options{'exponentVar'} if defined($options{'exponentVar'});
+ $exponentVar = '' unless defined($options{'exponentVar'});
+
+ if ($output eq 'array' or $output eq 'simplified') {
+ for my $i (0 .. ($#local_x + $#local_y)) {
+ $productArray[$i] = 0;
+ foreach my $j (0 .. $i) {
+ if ($j <= $#local_x and $i - $j <= $#local_y) {
+ $productArray[$i] = $productArray[$i] + $local_x[$j] * $local_y[ $i - $j ];
+ }
+ }
+ }
+
+ if ($output eq 'array') { return @productArray; }
+
+ if ($output eq 'simplified') {
+ return PolyString(\@productArray, order => $order, var => $var, exponentVar => $exponentVar); #cmh, 6/18/13
+ }
+ }
+
+ if ($output eq 'unsimplified') {
+ $productString = '';
+ for my $i (0 .. $#local_x) {
+ for my $j (0 .. $#local_y) {
+ # $productString = $productString.'+'.$local_x[$i]*$local_y[$j].$var.'^'.xPower($i+$j,$#local_x+$#local_y,$order);
+ # cmh, 6/18/13
+ $productString =
+ $productString . '+'
+ . $local_x[$i] * $local_y[$j]
+ . $var . '^{'
+ . xPower($i + $j, $#local_x + $#local_y, $order)
+ . $exponentVar . '}';
+ }
+ }
+ return $productString;
+ }
+}
+
+#
+# Accepts an array of the form that PolyTerms outputs
+# Inserts '&' between array entries for use in array environments
+# Converts Math Object entries to their TeX form
+# Inserts a spacing command after nonempty entries - the command
+# \setlength\arraycolsep{0em} should precede the \begin{array}
+# $Spacing should be something like "\hspace{0.4em}"
+# Also creates an alignment string for the \begin{array} command
+# $align should be "r", "c", or "l"
+# Not part of PGpolynomialMacros.pl
+#
+
+sub PolyTermsTeXPrep {
+ my ($temp, $spacing, $align) = @_;
+ my @terms = @{$temp};
+ my @void = ();
+ $alignment = "$align";
+ foreach my $i (0 .. $#terms - 1) {
+ @void = splice(@terms, 2 * $i + 1, 0, ('&'));
+ if ($terms[ 2 * $i ] ne '') {
+ if (($terms[ 2 * $i ] ne '-') && ($terms[ 2 * $i ] ne '+')) {
+ $terms[ 2 * $i ] = Compute($terms[ 2 * $i ])->TeX;
+ } else {
+ $terms[ 2 * $i ] = '{}' . $terms[ 2 * $i ] . '{}';
+ }
+ $terms[ 2 * $i ] = $terms[ 2 * $i ] . $spacing;
+ }
+ $alignment = $alignment . "$align";
+ }
+ $terms[$#terms] = Compute($terms[$#terms])->TeX;
+ $terms[$#terms] = $terms[$#terms] . $spacing;
+ return (@terms, $alignment);
+
+}
+
+# PolyTerms(~~@coefficientArray,x,order)
+
+#
+# Accepts an array containing the coefficients of a polynomial
+# in descending order
+# Assumes coefficients have numerical values, but may be Math Objects
+# Returns an array of size 2n-1 of the form
+# (a_nx^n, +/-, |a_{n-1}| x^{n-1}, +/-, ...)
+# where the variable is x is the second argument, the terms are Math Objects
+# Formulas, and the +/- are Perl strings.
+# The third argument can be "ascending" to reverse the order
+# If a_j is 0, then the corresponding term and preceding sign are empty
+# strings.
+# Trims beginning and ending, so that first and last terms are nonzero
+# Not part of PGpolynomialMacros.pl
+#
+
+#=cut
+
+sub PolyTerms {
+ my ($temp, $v, $order) = @_;
+ my @poly = @{$temp};
+ while (($poly[0] == 0) && ($#poly != 0)) { shift(@poly); }
+ my $degree = $#poly;
+
+ while (($poly[$#poly] == 0) && ($#poly != 0)) { pop(@poly); }
+ my $lowestdegree = $degree - $#poly;
+
+ if ($order eq "ascending") { @poly = reverse(@poly); }
+
+ my @terms = ();
+ my @abspoly = ();
+ foreach my $i (0 .. $#poly) {
+ $abspoly[$i] = abs($poly[$i]);
+ my $j = $#poly - $i + $lowestdegree;
+ if ($order eq "ascending") { $j = $i + $lowestdegree; }
+ if ($i == 0) {
+ if ($abspoly[$i] != 1) {
+ if ($j == 0) { $terms[ 2 * $i ] = Compute("$poly[$i]")->reduce; }
+ elsif ($j == 1) { $terms[ 2 * $i ] = Compute("$poly[$i] $v")->reduce; }
+ else { $terms[ 2 * $i ] = Compute("$poly[$i] $v^{$j}")->reduce; }
+ } elsif ($poly[$i] == 1) {
+ if ($j == 0) { $terms[ 2 * $i ] = Compute("1")->reduce; }
+ elsif ($j == 1) { $terms[ 2 * $i ] = Compute("$v")->reduce; }
+ else { $terms[ 2 * $i ] = Compute("$v^{$j}")->reduce; }
+ } else {
+ if ($j == 0) { $terms[ 2 * $i ] = Compute("-1")->reduce; }
+ elsif ($j == 1) { $terms[ 2 * $i ] = Compute("-$v")->reduce; }
+ else { $terms[ 2 * $i ] = Compute("-$v^{$j}")->reduce; }
+ }
+
+ } elsif ($j > 1) {
+ if ($poly[$i] > 0) {
+ $terms[ 2 * $i - 1 ] = '+';
+ if ($poly[$i] != 1) {
+ $terms[ 2 * $i ] = Compute("$poly[$i] $v^{$j}");
+ } else {
+ $terms[ 2 * $i ] = Compute("$v^{$j}");
+ }
+ } elsif ($poly[$i] == 0) {
+ $terms[ 2 * $i - 1 ] = '';
+ $terms[ 2 * $i ] = '';
+ } elsif ($poly[$i] == -1) {
+ $terms[ 2 * $i - 1 ] = '-';
+ $terms[ 2 * $i ] = Compute("$v^{$j}");
+ } else {
+ $terms[ 2 * $i - 1 ] = '-';
+ $terms[ 2 * $i ] = Compute("$abspoly[$i] $v^{$j}");
+ }
+ } elsif ($j == 1) {
+ if ($poly[$i] > 0) {
+ $terms[ 2 * $i - 1 ] = '+';
+ if ($poly[$i] != 1) {
+ $terms[ 2 * $i ] = Compute("$poly[$i] $v");
+ } else {
+ $terms[ 2 * $i ] = Compute("$v");
+ }
+ } elsif ($poly[$i] == 0) {
+ $terms[ 2 * $i - 1 ] = '';
+ $terms[ 2 * $i ] = '';
+ } elsif ($poly[$i] == -1) {
+ $terms[ 2 * $i - 1 ] = '-';
+ $terms[ 2 * $i ] = Compute("$v");
+ } else {
+ $terms[ 2 * $i - 1 ] = '-';
+ $terms[ 2 * $i ] = Compute("$abspoly[$i] $v");
+ }
+ } else {
+ if ($poly[$i] > 0) {
+ $terms[ 2 * $i - 1 ] = '+';
+ $terms[ 2 * $i ] = Compute("$poly[$i]");
+ } elsif ($poly[$i] == 0) {
+ $terms[ 2 * $i - 1 ] = '';
+ $terms[ 2 * $i ] = '';
+ } else {
+ $terms[ 2 * $i - 1 ] = '-';
+ $terms[ 2 * $i ] = Compute("$abspoly[$i]");
+ }
+ }
+ }
+
+ return @terms;
+}
+
+###############################
+# Name: PolySub
+sub PolySub {
+ my ($xref, $yref, %options) = @_;
+ my @local_x = @{$xref};
+ my @local_y = @{$yref};
+ foreach my $y (@local_y) { $y = $y * -1; }
+ return PolyAdd(\@local_x, \@local_y, %options);
+}
+
+###############################
+# Name: PolyAdd
+#
+# Input: two arrays, for example
+#
+# PolyAdd(~~@poly1, ~~@poly2)
+#
+# Arrays elements should be numerical or Math Objects
+# where addition are allowed. The idea is that
+# these are coefficients of a polynomial, either starting from the
+# constant term or from the leading term.
+#
+# Optional Input: order=>ascending or descending, defaults to descending
+# order is ignored if output=>array
+# Optional Input: var=> some string, defaulting to "x"
+# Optional Input: subtract=> 1 will subtract; default 0
+# Optional Input: output=>array, simplified, unsimplified
+# which is either an array of coefficients, a simplified
+# polynomial string using var and ready to be fed to Compute, or the same
+# but unsimplified with like terms near each other. Default is array.
+#
+# Output: described in Optional Input
+#
+# For example, given
+#
+# @poly1 = (1,2); # represents x+2 (assuming descending)
+# @poly2 = (3,-1,4); # represents 3x^2-x+4
+#
+# PolyAdd(~~@poly1, ~~@poly2,var=>'z', output=>unsimplified)
+#
+# '3z^2+1z+-1z+2+4'
+#
+# Note that instance of +- may be present, but Math Objectification
+# will handle that.
+#
+#
+# Given
+#
+# @poly1 = (Fraction(1,3),2); # represents 1/3+2x (assuming ascending)
+# @poly2 = (Fraction(1,2),0,Fraction(-1,3));
+#
+# PolyAdd(~~@poly1, ~~@poly2,order=>ascending)
+#
+# (Frac(5,6),2,Frac(-1,3))
+#
+#
+# Note: If you intend to feed the output string to Compute() or Formula(),
+# you should set the context reductions appropriately and apply ->reduce
+# *twice* to remove excess parentheses from negative coefficients.
+#
+################################
+
+sub PolyAdd {
+ my ($xref, $yref, %options) = @_;
+ my @local_x = @{$xref};
+ my @local_y = @{$yref};
+
+ my ($order, $var, $output, @sumArray, $sumString, $subtract, $plusMinus);
+
+ $order = $options{'order'} if defined($options{'order'});
+ $order = 'descending' unless defined($options{'order'});
+
+ $var = $options{'var'} if defined($options{'var'});
+ $var = 'x' unless defined($options{'var'});
+
+ $subtract = $options{'subtract'} if defined($options{'subtract'});
+ $subtract = 0 unless defined($options{'subtract'});
+
+ $output = $options{'output'} if defined($options{'output'});
+ $output = 'array' unless defined($options{'output'});
+
+ my $Nx = max($#local_x, $#local_y) - ($#local_x);
+ my $Ny = max($#local_x, $#local_y) - ($#local_y);
+
+ if ($order eq 'ascending') {
+ for my $i (1 .. $Nx) { push(@local_x, 0); }
+ for my $i (1 .. $Ny) { push(@local_y, 0); }
+ } else {
+ for my $i (1 .. $Nx) { unshift(@local_x, 0); }
+ for my $i (1 .. $Ny) { unshift(@local_y, 0); }
+ }
+
+ if ($output eq 'array' or $output eq 'simplified') {
+ for my $i (0 .. $#local_x) {
+ $sumArray[$i] = $local_x[$i] + (-1)**($subtract) * $local_y[$i];
+ }
+
+ if ($output eq 'array') { return @sumArray; }
+ if ($output eq 'simplified') { return PolyString(\@sumArray, order => $order, var => $var); }
+ }
+
+ if ($output eq 'unsimplified') {
+ if ($subtract == 1) { $plusMinus = '-' }
+ else { $plusMinus = '+' }
+ $sumString = '';
+ for my $i (0 .. $#local_x) {
+ $sumString =
+ $sumString . '+'
+ . $local_x[$i]
+ . $var . '^'
+ . xPower($i, $#local_x, $order)
+ . $plusMinus
+ . $local_y[$i]
+ . $var . '^'
+ . xPower($i, $#local_x, $order);
+ }
+ return $sumString;
+ }
+
+}
+
+###############################
+# Name: numberWord
+#
+# Input: either a whole number <100 or a fraction with numerator and denominator less than 100. If fraction, set denominator parameter.
+# Optional Input: capital => 1 (default is 0)
+# Optional Input: denominator=> 4 (default is 1)
+# numberWord($n)
+#
+# Output: a string spelling that number
+#
+################################
+
+sub numberWord {
+ my ($n, %options, $return, $capital, $denominator, $numerator) = @_;
+
+ $capital = $options{'capital'} if defined($options{'capital'});
+ $denominator = $options{'denominator'} if defined($options{'denominator'});
+
+ $capital = 0 unless defined($capital);
+ $denominator = 1 unless defined($denominator);
+
+ return $n if (abs($n * $denominator) > 99);
+
+ my %wordNumber = (
+ 0 => 'zero',
+ 1 => 'one',
+ 2 => 'two',
+ 3 => 'three',
+ 4 => 'four',
+ 5 => 'five',
+ 6 => 'six',
+ 7 => 'seven',
+ 8 => 'eight',
+ 9 => 'nine',
+ 10 => 'ten',
+ 11 => 'eleven',
+ 12 => 'twelve',
+ 13 => 'thirteen',
+ 14 => 'fourteen',
+ 15 => 'fifteen',
+ 16 => 'sixteen',
+ 17 => 'seventeen',
+ 18 => 'eighteen',
+ 19 => 'nineteen',
+ 20 => 'twenty',
+ 30 => 'thirty',
+ 40 => 'forty',
+ 50 => 'fifty',
+ 60 => 'sixty',
+ 70 => 'seventy',
+ 80 => 'eighty',
+ 90 => 'ninety'
+ );
+
+ my %wordFraction = (
+ 1 => 'first',
+ 2 => 'second',
+ 3 => 'third',
+ 4 => 'fourth',
+ 5 => 'fifth',
+ 6 => 'sixth',
+ 7 => 'seventh',
+ 8 => 'eighth',
+ 9 => 'ninth',
+ 12 => 'twelfth',
+ 20 => 'twentieth',
+ 30 => 'thirtieth',
+ 40 => 'fortieth',
+ 50 => 'fiftieth',
+ 60 => 'sixtieth',
+ 70 => 'seventieth',
+ 80 => 'eightieth',
+ 90 => 'ninetieth'
+ );
+
+ $return = '';
+ if ($n < 0) { $return = 'negative '; }
+
+ $n = abs($n);
+
+ if ($denominator == 1) {
+ if (($n <= 20) or ($n % 10 == 0)) { $return = $return . $wordNumber{$n} }
+ else { $return = $return . numberWord($n - ($n % 10)) . '-' . numberWord($n % 10) }
+
+ } else {
+ $numerator = round($n * $denominator);
+ $return = $return . numberWord($numerator);
+ if (($denominator == 2) and ($numerator != 1)) { $return = $return . ' halves'; }
+ elsif (($denominator == 2) and ($numerator == 1)) { $return = $return . ' half' }
+ elsif (defined($wordFraction{$denominator})) {
+ $return = $return . ' ' . $wordFraction{$denominator};
+ $return = $return . 's' if ($numerator != 1);
+ } elsif (defined($wordNumber{$denominator})) {
+ $return = $return . ' ' . $wordNumber{$denominator} . 'th';
+ $return = $return . 's' if ($numerator != 1);
+ } else {
+ $return =
+ $return . ' ' . numberWord($denominator - $denominator % 10) . '-' . $wordFraction{ $denominator % 10 };
+ $return = $return . 's' if ($numerator != 1);
+ }
+ }
+
+ $return = ucfirst($return) if ($capital == 1);
+ return $return;
+
+}
+
+# radicalListCheck
+#
+# Used when multiple answers are needed (e.g for quadratic) equations
+# that may or may *not* contain radicals.
+#
+# Sample use:
+#
+# ANS($ans->cmp(limits => [1,2],list_checker => ~~&radicalListCheck));
+#
+#
+sub radicalListCheck {
+ my ($correct, $student, $ansHash, $value) = @_;
+ my $score = 0; # number of correct student answers
+ my @errors = (); # stores error messages
+ my ($i, $j, $k); # loop counters
+ return (0, @errors) if $ansHash->{isPreview};
+ my $fullStudent = $ansHash->{student_formula};
+#in line below, correct_ans seems the right thing to do. In some problems, this is blank, so I'm just going with correct_value
+# for those problems. But changing to correct_value broke other problems... so the conditional hack
+ my $fullCorrect = ($ansHash->{correct_ans}) ? Formula($ansHash->{correct_ans}) : Formula($ansHash->{correct_value});
+
+ my @fullStudentValue = $fullStudent->value;
+ my @fullCorrectValue = $fullCorrect->value;
+ my $n = scalar(@fullStudentValue); # number of student answers
+ my $m = scalar(@fullCorrectValue); # number of correct answers
+
+ #
+ # Loop though the student answers
+ ##
+ for ($i = 0; $i < $n; $i++) {
+ my $ith = Value::List->NameForNumber($i + 1);
+ my $p = $fullStudentValue[$i]; # i-th student answer
+
+ #
+ # Check that the student's answer is an assignment (or whatever the right type)
+ #
+ my $assingmentMessageGiven = 0;
+ my $nosolutionMessageGiven = 0;
+ if (defined($p)) {
+ if ($p eq "no real solutions") {
+ push(@errors, "This equation does have some solutions.");
+ $nosolutionMessageGiven = 1;
+ } elsif ($p->type ne "Assignment") {
+ push(@errors, "Your $ith entry should be written $var=_____");
+ $assingmentMessageGiven = 1;
+ }
+ }
+
+ #
+ # Check that the answer hasn't been given before
+ #
+ for ($j = 0, $used = 0; $j < $i; $j++) {
+ if ($p == $fullStudentValue[$j]) {
+ push(@errors, "Your $ith solution is the same as a previous one");
+ $used = 1;
+ last;
+ }
+ }
+
+ #
+ # If not already used, check that it is a correct and reduced answer
+ #
+ if (!$used) {
+ my ($numericallyCorrect, $reduced);
+ for ($k = 0, $numericallyCorrect = 0; ($k < $m); $k++) {
+ $q = $fullCorrectValue[$k];
+ if (Formula($q) == Formula($p)) {
+ $numericallyCorrect = 1;
+ my ($setSqrt, $setRoot) = (Context()->flag("setSqrt"), Context()->flag("setRoot"));
+ Context()->flags->set(
+ checkSqrt => $setSqrt,
+ checkRoot => $setRoot,
+ bizarroAdd => 1,
+ bizarroSub => 1,
+ bizarroMul => 1,
+ bizarroDiv => 1
+ );
+ delete $p->{test_values};
+ delete $q->{test_values};
+ if ($p == $q) { $reduced = 1 }
+ else { $reduced = 0 }
+ ; # check if form is correct
+
+ Context()->flags->set(
+ checkSqrt => 0,
+ checkRoot => 0,
+ bizarroAdd => 0,
+ bizarroSub => 0,
+ bizarroMul => 0,
+ bizarroDiv => 0
+ );
+ $score++ if ($numericallyCorrect and $reduced);
+ push(@errors, "Your $ith solution is not fully simplified")
+ if ($numericallyCorrect and !$reduced and ($n > 1));
+ push(@errors, "Your solution is not fully simplified")
+ if ($numericallyCorrect and !$reduced and ($n == 1));
+ last;
+ }
+ }
+ push(@errors, "Your $ith solution is not correct")
+ if (!$numericallyCorrect and ($n > 1) and !$assingmentMessageGiven);
+ push(@errors, "Your solution is not correct")
+ if (!$numericallyCorrect and ($n == 1) and !$assingmentMessageGiven and !$nosolutionMessageGiven);
+ }
+ }
+
+ #
+ # Check that there are the right number of answers
+ #
+ if (!$ansHash->{isPreview}) {
+ #push(@errors,"Do you have all the solutions?") if (($n < 2) and ($score < 2) and !$nosolutionMessageGiven );
+ #push(@errors,"Should a quadratic equation have more than two solutions?") if ($n > 2);
+ push(@errors, "Do you have all the solutions?") if (($n < $m) and ($score < $m) and !$nosolutionMessageGiven);
+ push(@errors, "Should this equation have more than $m solution?") if ($n > $m and $m == 1);
+ push(@errors, "Should this equation have more than $m solutions?") if ($n > $m and $m > 1);
+
+ }
+ return ($score, @errors);
+}
+
+# Keyboard instructions should only be displayed in HTML output
+sub KeyboardInstructions {
+ my $in = shift;
+ if ($displayMode =~ /HTML/) { return $in }
+}
+
+1;
diff --git a/macros/misc/PGunion.pl b/macros/misc/PGunion.pl
new file mode 100644
index 0000000000..fb101a5866
--- /dev/null
+++ b/macros/misc/PGunion.pl
@@ -0,0 +1,10 @@
+#
+# Load most of the interesting code developed at Union.
+#
+
+loadMacros(
+ "unionMacros.pl", "unionUtils.pl", "unionProblem.pl", "unionImage.pl",
+ "unionLists.pl", "unionTables.pl", "unionMessages.pl",
+);
+
+1;
diff --git a/macros/misc/unionInclude.pl b/macros/misc/unionInclude.pl
new file mode 100644
index 0000000000..27ef97cb31
--- /dev/null
+++ b/macros/misc/unionInclude.pl
@@ -0,0 +1,95 @@
+######################################################################
+#
+# Routines to make it easier to include additional PG files within
+# a given one. These files don't have to be in the courseScripts
+# directory; rather, they are assumed to be relative to the directory
+# containing the calling PG file.
+#
+
+sub _unionInclude_init { }; # don't reload this file
+
+######################################################################
+#
+# Usage: includePGfile(name)
+#
+# where name is the name of a PG file (relative to the directory
+# of the file containing this call).
+#
+sub includePGfile {
+ my $name = shift;
+ my $PGfile = $main::envir{'fileName'};
+ $PGfile =~ s![^/]+$!!;
+ $PGfile .= $name;
+ while ($PGfile =~ s![^/]*/../!!) { }
+ $PGfile =~ s!^tmpEdit/!!;
+ # warn("file is $PGfile, directory is $main::templateDirectory");
+ my $problem = read_whole_problem_file($main::templateDirectory . $PGfile);
+
+ my ($oldpname, $oldname) =
+ ($main::envir{'probFileName'}, $main::envir{'fileName'});
+ $main::envir{'probFileName'} = $PGfile;
+ $main::envir{'fileName'} = $PGfile;
+ includePGtext($problem, %main::envir);
+ ($main::envir{'probFileName'}, $main::envir{'fileName'}) =
+ ($oldpname, $oldname);
+}
+
+######################################################################
+#
+#
+# Usage: includeRandomProblem(file1,file2,...,fileN);
+#
+# where the fileNs are the names of the files from which
+# to choose (relative to the directory of the file containing
+# this call).
+#
+# To use this, make one PG file that include the call to this
+# random routine, and then include it the set definition file
+# as many times as you want (up to N times). A different problem
+# will be included for each instance in the set definition file.
+#
+sub includeRandomProblem {
+ my @flist = @_;
+ my @shuffle = _NchooseK(scalar(@flist), scalar(@flist));
+ my $start = $initialProblemNumber;
+ $start = 1 unless defined $start;
+ WARN_MESSAGE(
+ "There is an error in initializing this random problem. initialProblemNumber $start is greater than the current problem number $probNum"
+ ) if $probNum - $start < 0;
+ my $n = (@shuffle)[ $probNum - $start ];
+ includePGfile($flist[$n]);
+ #$main::problemPostamble->{HTML} = ""; # Hack to prevent ENDDOCUMENT from adding it again
+ # commenting this out is another hack which prevents nesting on quizzes -- go figure
+}
+
+#
+# Legacy code no longer needed. The included file can contain
+# BEGIN_INCLUSION(); and END_INCLUSION(); in place of DOCUMENT()
+# and ENDDOCUMENT(); calls.
+#
+sub BEGIN_INCLUSION { }
+sub END_INCLUSION { }
+
+######################################################################
+#
+# This is a service routine for includeRandomProblem() above.
+# It an array of k numbers chosen from 0 to n-1, but preserves the
+# random seed so that the included problem won't be affected by
+# this function, and replaces it by the psvn, so that the list
+# produced will be the same each time it is called.
+#
+sub _NchooseK {
+ my ($n, $k) = @_;
+ my @array = 0 .. ($n - 1);
+ my @out = ();
+ my $seed = ($main::psvn || 23) * 101 + ($initialProblemNumber || 1);
+ my $oldseed = $main::PG_random_generator->{seed};
+ $main::PG_random_generator->srand($seed);
+ while (@out < $k) {
+ push(@out, splice(@array, $main::PG_random_generator->random(0, $#array, 1), 1));
+ }
+ $main::PG_random_generator->srand($oldseed);
+ return @out;
+}
+
+1;
diff --git a/macros/misc/unionMacros.pl b/macros/misc/unionMacros.pl
new file mode 100644
index 0000000000..8c83bf3b8f
--- /dev/null
+++ b/macros/misc/unionMacros.pl
@@ -0,0 +1,162 @@
+######################################################################
+#
+# Some macros that add to the ones like $PAR, $BR, etc.
+#
+
+sub _unionMacros_init { }; # don't reload this file
+
+#
+# Shorthand for WeBWorK
+#
+$WW = "WeBWorK";
+
+#
+# Shorthands for entering and leaving rawhtml mode in
+# LaTeX2HTML (since they are so commonly used).
+#
+$bHTML = '\begin{rawhtml}';
+$eHTML = '\end{rawhtml}';
+
+#
+# HTML(htmlcode)
+# HTML(htmlcode,texcode)
+#
+# Insert $html in HTML mode or \begin{rawhtml}$html\end{rawhtml} in
+# Latex2HTML mode. In TeX mode, insert nothing for the first form, and
+# $tex for the second form.
+#
+sub HTML {
+ my ($html, $tex) = @_;
+ return ('') unless (defined($html) && $html ne '');
+ $tex = '' unless (defined($tex));
+ MODES(TeX => $tex, Latex2HTML => $bHTML . $html . $eHTML, HTML => $html);
+}
+
+#
+# Begin and end indented text
+#
+
+$BBLOCKQUOTE = HTML('', '\par\bgroup\advance\leftskip by 2em ');
+$EBLOCKQUOTE = HTML(' ', '\par\egroup ');
+
+#
+# Start and stop centering
+#
+$BCENTER = HTML('', '\begin{center}');
+$ECENTER = HTML(' ', '\end{center}');
+
+#
+# Begin and end mode
+#
+$BTT = HTML('', '\texttt{');
+$ETT = HTML(' ', '}');
+
+#
+# Begin and end mode
+#
+$BSMALL = HTML('', '{\small ');
+$ESMALL = HTML(' ', '}');
+
+#
+# Remove extra space in bold in latex2html mode
+#
+$BBOLD = HTML('', '{\bf ');
+$EBOLD = HTML(' ', '}');
+
+#
+# tth doesn't seem to understand \colon
+#
+$COLON = MODES(TeX => '\colon ', HTML => ':', HTML_dpng => '\colon ');
+
+#
+# Alternatives to the standard WW versions of these
+#
+$LT = $LTS;
+$GT = $GTS;
+
+$LE = $LTE;
+$GE = $GTE;
+
+#
+# Common math sets
+#
+$R = MODES(TeX => '{\bf R}', HTML_tth => '{\bf R}', HTML => 'R ');
+$Z = MODES(TeX => '{\bf Z}', HTML_tth => '{\bf Z}', HTML => 'Z ');
+$N = MODES(TeX => '{\bf N}', HTML_tth => '{\bf N}', HTML => 'N ');
+$Q = MODES(TeX => '{\bf Q}', HTML_tth => '{\bf Q}', HTML => 'Q ');
+$C = MODES(TeX => '{\bf C}', HTML_tth => '{\bf C}', HTML => 'C ');
+
+#
+# Superscripts and subscript (mostly for if you want answer
+# rules in these positions).
+#
+$BSUP = HTML('', '$^{');
+$ESUP = HTML(' ', '}$');
+
+$BSUB = HTML('', '$_{');
+$ESUB = HTML(' ', '}$');
+
+#
+# Browser-only BR
+#
+$BBR = HTML(' ');
+
+#
+# Broser-only \displaystyle
+#
+$DISPLAY = MODES(
+ TeX => '',
+ Latex2HTML => '\displaystyle ',
+ HTML_tth => '\displaystyle ',
+ HTML => ''
+);
+
+#
+# Provides a title to the problem
+#
+sub Title {
+ my $title = shift;
+
+ TEXT(MODES(
+ TeX => "\\par\\begin{centering}{\\bf $title}\\par\\end{centering}\\nobreak\n",
+ Latex2HTML => $bHTML . '' . $title . ' ' . $eHTML,
+ HTML => '' . $title . ' '
+ ));
+}
+
+#
+# A warning that we are using javaScript
+#
+sub JAVASCRIPT_PROBLEM {
+ TEXT(HTML(
+ '
+ This problem requires that JavaScript be enabled!
+
+ '
+ ));
+}
+
+#
+# Modify a polynomial to remove coeficients of 1, -1 and 0
+# The polynomial can be a multivariable one. The parameters
+# following the formula itself are the names of the variables
+# for the formula. Any number can be provided, and the default
+# is x, y, and z. Variable names must be one character long,
+# and the formula really should be a polynomial, as the algorithm
+# for removing coefficients of 0 relies on that in an important way.
+#
+sub FPOLY {
+ my $poly = shift;
+ $poly = FEQ($poly);
+ @_ = ('x', 'y', 'z') unless scalar(@_) > 0;
+ my $x = '[' . join('', @_) . ']';
+ $poly =~ s/(^|[^\d.])1\s*(\*\s*)?($x)/$1$3/g;
+ $poly =~ s/-1\s*($x)/-$1/g;
+ $poly =~ s/(^|\+|\-)\s*0(\s*(\*\s*)?$x((\^|\*\*)\d+(\.\d*)?)?)+//g;
+ $poly =~ s/(^|\+|\-)\s*0+\s*([-+]|$)/$2/;
+ $poly =~ s/^\s*\+\s*//;
+ $poly = "0" if ($poly eq "");
+ return $poly;
+}
+
+1;
diff --git a/macros/misc/unionMessages.pl b/macros/misc/unionMessages.pl
new file mode 100644
index 0000000000..a62c6057d9
--- /dev/null
+++ b/macros/misc/unionMessages.pl
@@ -0,0 +1,75 @@
+sub _unionMessages_init { }
+
+loadMacros("unionMacros.pl");
+
+######################################################################
+#
+# How to say "infinity"
+#
+
+$INFINITY_WORD = $$Value::context->flag('infiniteWord') unless defined($INFINITY_WORD);
+
+######################################################################
+#
+# A message that can be included (within BEGIN_TEXT and END_TEXT)
+# to tell the student how to enter infinity and minus infinity.
+#
+$INFINITY_MESSAGE =
+ $BITALIC
+ . $BSMALL
+ . "Use ${LQ}$INFINITY_WORD${RQ} for $LQ\\(\\infty\\)$RQ "
+ . "and ${LQ}-$INFINITY_WORD${RQ} for $LQ\\(-\\infty\\)$RQ."
+ . $ESMALL
+ . $EITALIC;
+
+$INFINITY_MESSAGE = "" if $displayMode eq 'TeX';
+
+######################################################################
+#
+# A message to tell students how to enter "Does not exist".
+#
+$DNE_MESSAGE = $BITALIC . $BSMALL . "Use ${LQ}DNE${RQ} for ${LQ}Does not exist${RQ}." . $ESMALL . $EITALIC;
+
+$DNE_MESSAGE = "" if $displayMode eq 'TeX';
+
+######################################################################
+#
+# A message to tell students how to enter unions and infinities
+#
+$INFINITY_UNION_MESSAGE =
+ "${BSMALL}${BBOLD}Note:${EBOLD} "
+ . "If the answer includes more than one interval, write the intervals "
+ . "separated by the ${LQ}union$RQ symbol, ${BITALIC}U${EITALIC}. If needed, enter "
+ . "\\(\\infty\\) as ${BITALIC}$INFINITY_WORD${EITALIC} and \\(-\\infty\\) as "
+ . "${BITALIC}-$INFINITY_WORD${EITALIC}.${ESMALL}";
+
+$INFINITY_UNION_MESSAGE = "" if $displayMode eq 'TeX';
+
+######################################################################
+#
+# A message to tell students how to enter lists of intervals
+#
+$INTERVAL_LIST_MESSAGE =
+ $BSMALL
+ . $BBOLD . "Note:"
+ . $EBOLD
+ . "Your answer should be a list of one or more intervals separated by commas. "
+ . "Enter ${BITALIC}NONE${EITALIC} if there are no intervals.";
+
+$INTERVAL_LIST_MESSAGE = "" if $displayMode eq 'TeX';
+
+######################################################################
+#
+# A message for lists of unions
+#
+$UNION_LIST_MESSAGE =
+ $BSMALL
+ . $BBOLD . "Note:"
+ . $EBOLD
+ . "Your answer should be a list of one or more intervals or unions of intervals. "
+ . "Separate the entries in your list by commas. "
+ . "Enter ${BITALIC}NONE${EITALIC} if there are no intervals or unions.";
+
+$UNION_LIST_MESSAGE = "" if $displayMode eq 'TeX';
+
+1;
diff --git a/macros/misc/unionProblem.pl b/macros/misc/unionProblem.pl
new file mode 100644
index 0000000000..60701e50b6
--- /dev/null
+++ b/macros/misc/unionProblem.pl
@@ -0,0 +1,15 @@
+#!/usr/local/bin/perl
+
+sub _unionProblem_init { }
+
+##################################################
+#
+# No longer needed since WW can output the grey
+# box automatically by changes in global.conf
+#
+sub BEGIN_PROBLEM { }
+sub END_PROBLEM { }
+
+sub problem_NoTable { }
+
+1;
diff --git a/macros/misc/unionUtils.pl b/macros/misc/unionUtils.pl
new file mode 100644
index 0000000000..18b4a5e2dd
--- /dev/null
+++ b/macros/misc/unionUtils.pl
@@ -0,0 +1,136 @@
+######################################################################
+##
+## These are some miscellaneous routines that may be useful.
+##
+
+sub _unionUtils_init { }; # don't reload this file
+
+#
+# Remove leading and trailing spaces
+#
+sub trimString {
+ my $s = shift;
+ $s =~ s/^\s+|\s+$//g;
+ return $s;
+}
+
+#
+# Check if a string is a number
+#
+sub isNumber {
+ my $n = shift;
+ $n = ($n =~ m/^[+-]?(\d+(\.\d*)?|\.\d+)([Ee][-+]?\d+)?$/);
+ return $n;
+}
+
+#
+# names for numbers
+#
+sub NameForNumber {
+ my $n = shift;
+ my $name =
+ ('zeroth', 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth', 'ninth', 'tenth')[$n];
+ $name = "$n-th" if ($n > 10);
+ return $name;
+}
+
+#
+# A debugging routine that allows the "warn" function to print a variable that
+# contains HTML code properly
+#
+sub showHTML {
+ my $string = shift;
+ $string =~ s/&/\&/g;
+ $string =~ s/\</g;
+ $string =~ s/>/\>/g;
+ $string;
+}
+
+#
+# Make a named subroutine that returns the value of a function. If
+# no parameters are passed to the function, the function's string
+# is returned. (This will be obsolete when I finish the
+# new expression parser -- DPVC.)
+#
+sub perlFunction {
+ my $def = shift;
+ my $debug = shift;
+ if ($def !~ m/^\s*(\w+)\s*\(\s*((\w+\s*,?\s*)+)\s*\)\s*:?=\s*(.*)$/) {
+ warn "perlFunction: can't parse '$def'";
+ return;
+ }
+ my ($name, $vars, $expr) = ($1, $2, $4);
+ my $f = $expr;
+ my @X;
+ my $x;
+ $vars =~ s/\s//g;
+ foreach $x (split(',', $vars)) {
+ push(@X, "\$$x");
+ $expr =~ s/(\b|\d)$x\b/$1(\$$x)/g;
+ }
+ #
+ # Fix up the expression
+ #
+ $expr =~ s/\bpi\b/(4*atan2(1,1))/g;
+ $expr =~ s/\be\b/(exp(1))/g;
+ $expr =~ s!\\frac\{([^\}]*)\}\s*\{([^\}]*)\}!($1)/($2)!g; # fractions
+ $expr =~ s/\{([^\}]*)\}/($1)/g; # TeX parameters
+ $expr =~ s/\\//g; # TeX slashes
+ $expr =~ s/\^/**/g; # change ^ to **
+ #
+ # Do implied multiplication
+ #
+ $expr =~ s/\)\s*\(/\)*\(/g;
+ $expr =~ s/\)\s*([a-zA-Z0-9.])/\)*$1/g;
+ $expr =~ s/([\d.])(\s\d)/$1*$2/g;
+ $expr =~ s/([\d.])\s*([a-zA-Z\(])/$1*$2/g;
+ #
+ # Fix +-, -+, ++ and --
+ #
+ $expr =~ s/\+\s*([\+-])/$1/g;
+ $expr =~ s/-\s*\+/-/g;
+ $expr =~ s/-\s*-/+/g;
+ #
+ # Create the named function
+ #
+ PG_restricted_eval(
+ "sub $name { return('$f') unless (\@_);
+ my (" . join(',', @X) . ") = \@_; $expr}"
+ );
+ warn("sub $name { my (" . join(',', @X) . ") = \@_; $expr}") if $debug;
+}
+
+#
+# A hack to pick display mode in HTML modes, but plain math mode
+# in TeX modes. This makes fractions appear better in HTML mode,
+# without making it worse in TeX modes. This is for use with
+# AlignedList objects.
+#
+sub DMATH {
+ my $math = shift;
+ #
+ # HTML_tth puts an unwanted at the beginning,
+ # and uses a centered table. So here we remove the
+ # and re-align the table to the right.
+ #
+ if ($displayMode eq "HTML_tth") {
+ $math = trimString(EV2('\[' . $math . '\]'));
+ $math =~ s! !!;
+ $math =~ s!table align="center"!table align="right"!;
+ $math =~ s!(table [^>]*) width="100%"!$1!;
+ } elsif ($displayMode eq "HTML") {
+ $math = '\(' . $math . '\)';
+ } elsif ($displayMode =~ m/^HTML/) {
+ $math = '\(\displaystyle ' . $math . '\)';
+ }
+
+ MODES(
+ TeX => '\(\displaystyle ' . $math . '\)',
+ Latex2HTML => '\(\displaystyle ' . $math . '\)',
+ HTML => $math,
+ HTML_tth => $math,
+ HTML_dpng => $math,
+ );
+}
+
+1;
diff --git a/macros/Parser.pl b/macros/parsers/Parser.pl
similarity index 100%
rename from macros/Parser.pl
rename to macros/parsers/Parser.pl
diff --git a/macros/parserAssignment.pl b/macros/parsers/parserAssignment.pl
similarity index 100%
rename from macros/parserAssignment.pl
rename to macros/parsers/parserAssignment.pl
diff --git a/macros/parserAutoStrings.pl b/macros/parsers/parserAutoStrings.pl
similarity index 100%
rename from macros/parserAutoStrings.pl
rename to macros/parsers/parserAutoStrings.pl
diff --git a/macros/parserCustomization.pl b/macros/parsers/parserCustomization.pl
similarity index 100%
rename from macros/parserCustomization.pl
rename to macros/parsers/parserCustomization.pl
diff --git a/macros/parserDifferenceQuotient.pl b/macros/parsers/parserDifferenceQuotient.pl
similarity index 100%
rename from macros/parserDifferenceQuotient.pl
rename to macros/parsers/parserDifferenceQuotient.pl
diff --git a/macros/parserFormulaAnyVar.pl b/macros/parsers/parserFormulaAnyVar.pl
similarity index 100%
rename from macros/parserFormulaAnyVar.pl
rename to macros/parsers/parserFormulaAnyVar.pl
diff --git a/macros/parserFormulaUpToConstant.pl b/macros/parsers/parserFormulaUpToConstant.pl
similarity index 100%
rename from macros/parserFormulaUpToConstant.pl
rename to macros/parsers/parserFormulaUpToConstant.pl
diff --git a/macros/parserFormulaWithUnits.pl b/macros/parsers/parserFormulaWithUnits.pl
similarity index 100%
rename from macros/parserFormulaWithUnits.pl
rename to macros/parsers/parserFormulaWithUnits.pl
diff --git a/macros/parserFunction.pl b/macros/parsers/parserFunction.pl
similarity index 100%
rename from macros/parserFunction.pl
rename to macros/parsers/parserFunction.pl
diff --git a/macros/parserFunctionPrime.pl b/macros/parsers/parserFunctionPrime.pl
similarity index 100%
rename from macros/parserFunctionPrime.pl
rename to macros/parsers/parserFunctionPrime.pl
diff --git a/macros/parserGraphTool.pl b/macros/parsers/parserGraphTool.pl
similarity index 100%
rename from macros/parserGraphTool.pl
rename to macros/parsers/parserGraphTool.pl
diff --git a/macros/parserImplicitEquation.pl b/macros/parsers/parserImplicitEquation.pl
similarity index 100%
rename from macros/parserImplicitEquation.pl
rename to macros/parsers/parserImplicitEquation.pl
diff --git a/macros/parserImplicitPlane.pl b/macros/parsers/parserImplicitPlane.pl
similarity index 100%
rename from macros/parserImplicitPlane.pl
rename to macros/parsers/parserImplicitPlane.pl
diff --git a/macros/parserLinearInequality.pl b/macros/parsers/parserLinearInequality.pl
similarity index 100%
rename from macros/parserLinearInequality.pl
rename to macros/parsers/parserLinearInequality.pl
diff --git a/macros/parserMultiAnswer.pl b/macros/parsers/parserMultiAnswer.pl
similarity index 100%
rename from macros/parserMultiAnswer.pl
rename to macros/parsers/parserMultiAnswer.pl
diff --git a/macros/parserMultiPart.pl b/macros/parsers/parserMultiPart.pl
similarity index 100%
rename from macros/parserMultiPart.pl
rename to macros/parsers/parserMultiPart.pl
diff --git a/macros/parserNumberWithUnits.pl b/macros/parsers/parserNumberWithUnits.pl
similarity index 100%
rename from macros/parserNumberWithUnits.pl
rename to macros/parsers/parserNumberWithUnits.pl
diff --git a/macros/parserOneOf.pl b/macros/parsers/parserOneOf.pl
similarity index 100%
rename from macros/parserOneOf.pl
rename to macros/parsers/parserOneOf.pl
diff --git a/macros/parserParametricLine.pl b/macros/parsers/parserParametricLine.pl
similarity index 100%
rename from macros/parserParametricLine.pl
rename to macros/parsers/parserParametricLine.pl
diff --git a/macros/parserParametricPlane.pl b/macros/parsers/parserParametricPlane.pl
similarity index 100%
rename from macros/parserParametricPlane.pl
rename to macros/parsers/parserParametricPlane.pl
diff --git a/macros/parserPopUp.pl b/macros/parsers/parserPopUp.pl
similarity index 100%
rename from macros/parserPopUp.pl
rename to macros/parsers/parserPopUp.pl
diff --git a/macros/parserPrime.pl b/macros/parsers/parserPrime.pl
similarity index 100%
rename from macros/parserPrime.pl
rename to macros/parsers/parserPrime.pl
diff --git a/macros/parserQuotedString.pl b/macros/parsers/parserQuotedString.pl
similarity index 100%
rename from macros/parserQuotedString.pl
rename to macros/parsers/parserQuotedString.pl
diff --git a/macros/parserRadioButtons.pl b/macros/parsers/parserRadioButtons.pl
similarity index 100%
rename from macros/parserRadioButtons.pl
rename to macros/parsers/parserRadioButtons.pl
diff --git a/macros/parserRoot.pl b/macros/parsers/parserRoot.pl
similarity index 100%
rename from macros/parserRoot.pl
rename to macros/parsers/parserRoot.pl
diff --git a/macros/parserSolutionFor.pl b/macros/parsers/parserSolutionFor.pl
similarity index 100%
rename from macros/parserSolutionFor.pl
rename to macros/parsers/parserSolutionFor.pl
diff --git a/doc/MathObjects/macros/parserUtils.pl b/macros/parsers/parserUtils.pl
similarity index 79%
rename from doc/MathObjects/macros/parserUtils.pl
rename to macros/parsers/parserUtils.pl
index 136d84f5f0..487786b38f 100644
--- a/doc/MathObjects/macros/parserUtils.pl
+++ b/macros/parsers/parserUtils.pl
@@ -1,4 +1,8 @@
-loadMacros("unionImage.pl", "unionTables.pl",);
+
+# not sure why these are loaded. They are not used in this file. If these are loaded
+# there is an error during the load_macros.t test.
+
+# loadMacros("unionImage.pl", "unionTables.pl",);
$bHTML = '\begin{rawhtml}';
$eHTML = '\end{rawhtml}';
@@ -46,7 +50,10 @@ sub HTML {
#
sub protectHTML {
my $string = shift;
- return encode_pg_and_html($string);
+ $string =~ s/&/\&/g;
+ $string =~ s/\</g;
+ $string =~ s/>/\>/g;
+ $string;
}
sub _parserUtils_init { }
diff --git a/macros/parserVectorUtils.pl b/macros/parsers/parserVectorUtils.pl
similarity index 100%
rename from macros/parserVectorUtils.pl
rename to macros/parsers/parserVectorUtils.pl
diff --git a/macros/parserWordCompletion.pl b/macros/parsers/parserWordCompletion.pl
similarity index 100%
rename from macros/parserWordCompletion.pl
rename to macros/parsers/parserWordCompletion.pl
diff --git a/macros/PGchoicemacros.pl b/macros/ui/PGchoicemacros.pl
similarity index 100%
rename from macros/PGchoicemacros.pl
rename to macros/ui/PGchoicemacros.pl
diff --git a/macros/PGinfo.pl b/macros/ui/PGinfo.pl
similarity index 100%
rename from macros/PGinfo.pl
rename to macros/ui/PGinfo.pl
diff --git a/macros/alignedChoice.pl b/macros/ui/alignedChoice.pl
similarity index 100%
rename from macros/alignedChoice.pl
rename to macros/ui/alignedChoice.pl
diff --git a/macros/ui/choiceUtils.pl b/macros/ui/choiceUtils.pl
new file mode 100644
index 0000000000..c67631dba8
--- /dev/null
+++ b/macros/ui/choiceUtils.pl
@@ -0,0 +1,90 @@
+sub _choiceUtils_init { }; # don't reload this file
+
+#
+# A replacement for std_print_q that uses tables to align the questions, so
+# that if a question wraps, it is properly indented.
+#
+
+sub alt_print_q {
+ my $self = shift;
+ my @questions = @_;
+ my $length = $self->{ans_rule_len};
+ my $sep = $self->{separation};
+ $sep = 0 unless defined($sep);
+ my $valign = $self->{valign};
+ $valign = "TOP" unless defined($valign);
+ my $i = 1;
+ my $quest;
+
+ my $out = "";
+ if ($main::displayMode =~ m/^HTML/) {
+ $out = "\n \n
\n";
+ foreach $quest (@questions) {
+ $out .=
+ ''
+ . ans_rule($length)
+ . " "
+ . $i++
+ . ". $quest \n";
+ }
+ $out .= "
\n";
+ } elsif ($main::displayMode eq 'Latex2HTML') {
+ $out = "\\par\n\\begin{rawhtml}" . "\\end{rawhtml}\n";
+ foreach $quest (@questions) {
+ $out .=
+ '\begin{rawhtml}'
+ . '\end{rawhtml}'
+ . ans_rule($length)
+ . '\begin{rawhtml} '
+ . ''
+ . $i++
+ . '. \\end{rawhtml} '
+ . $quest
+ . '\begin{rawhtml} \\end{rawhtml}' . "\n";
+ }
+ $out .= "\\begin{rawhtml}
\n\\end{rawhtml}";
+ } elsif ($main::displayMode eq 'TeX') {
+ $out = "\n\\par\\begin{enumerate}\n\\advance\\leftskip by 2em";
+ foreach $quest (@questions) { $out .= "\\item[" . ans_rule($length) . ' ' . $i++ . ".] $quest\n" }
+ $out .= "\\end{enumerate}\n";
+ } else {
+ $out = "Error: alt_print_q: Unknown displayMode: $main::displayMode.";
+ }
+ $out;
+}
+
+sub alt_print_a {
+ my $self = shift;
+ my (@array) = @_;
+ my $sep = $self->{separation} || 0;
+ my $valign = $self->{valign} || "TOP";
+ my $i = 0;
+
+ my $out = MODES(
+ TeX => "\\begin{enumerate}\n",
+ Latex2HTML => qq{\\begin{rawhtml}\\end{rawhtml}\n},
+ HTML => qq{},
+ );
+ my $elem;
+ foreach $elem (@array) {
+ my $c = $main::ALPHABET[$i];
+ $out .= MODES(
+ TeX => "\\item[$c.] $elem\n",
+ Latex2HTML => qq{\\begin{rawhtml}$c. }
+ . qq{\\end{rawhtml}$elem\\begin{rawhtml} \\end{rawhtml}\n},
+ HTML => qq{$c. $elem \n},
+ );
+ $i++;
+ }
+ $out .= MODES(
+ TeX => "\\end{enumerate}\n",
+ Latex2HTML => "\\begin{rawhtml}
\\end{rawhtml}\n",
+ HTML => "
\n",
+ );
+ $out;
+}
+
+1;
diff --git a/macros/niceTables.pl b/macros/ui/niceTables.pl
similarity index 99%
rename from macros/niceTables.pl
rename to macros/ui/niceTables.pl
index d0da8edbcb..33380b03bb 100644
--- a/macros/niceTables.pl
+++ b/macros/ui/niceTables.pl
@@ -532,7 +532,7 @@ sub DataTable {
${ $dataref->[$i][$j] }{cellcss} =
"border-left:solid 1px; " . ${ $dataref->[$i][$j] }{cellcss};
}
- if (defined $alignmentcolumns[$m] && $alignmentcolumns[$m] != 0) {
+ if ($alignmentcolumns[$m] != 0) {
${ $dataref->[$i][$j] }{cellcss} =
"border-right:solid 1px; " . ${ $dataref->[$i][$j] }{cellcss};
}
diff --git a/macros/ui/pccTables.pl b/macros/ui/pccTables.pl
new file mode 100644
index 0000000000..20952b7f13
--- /dev/null
+++ b/macros/ui/pccTables.pl
@@ -0,0 +1,5 @@
+# This is just a redirect to niceTables.pl, which was originally called pccTables.pl
+
+loadMacros("niceTables.pl");
+
+1;
diff --git a/macros/problemPanic.pl b/macros/ui/problemPanic.pl
similarity index 100%
rename from macros/problemPanic.pl
rename to macros/ui/problemPanic.pl
diff --git a/macros/quickMatrixEntry.pl b/macros/ui/quickMatrixEntry.pl
old mode 100755
new mode 100644
similarity index 100%
rename from macros/quickMatrixEntry.pl
rename to macros/ui/quickMatrixEntry.pl
diff --git a/macros/source.pl b/macros/ui/source.pl
similarity index 100%
rename from macros/source.pl
rename to macros/ui/source.pl
diff --git a/macros/text2PG.pl b/macros/ui/text2PG.pl
similarity index 100%
rename from macros/text2PG.pl
rename to macros/ui/text2PG.pl
diff --git a/macros/unionLists.pl b/macros/ui/unionLists.pl
similarity index 100%
rename from macros/unionLists.pl
rename to macros/ui/unionLists.pl
diff --git a/macros/unionTables.pl b/macros/ui/unionTables.pl
similarity index 100%
rename from macros/unionTables.pl
rename to macros/ui/unionTables.pl
diff --git a/t/macros/load_macros.t b/t/macros/load_macros.t
index 7ba810877b..c8ab7e6575 100644
--- a/t/macros/load_macros.t
+++ b/t/macros/load_macros.t
@@ -15,16 +15,39 @@ die "PG_ROOT not found in environment.\n" unless $ENV{PG_ROOT};
use lib "$ENV{PG_ROOT}/lib";
use Test2::V0;
+use File::Find;
use WeBWorK::PG;
-my %baseMacros = ('PG.pl' => 1, 'PGstandard.pl' => 1);
+my %baseMacros = (
+ 'PG.pl' => 1,
+ 'PGstandard.pl' => 1,
+ 'PGbasicmacros.pl' => 1,
+ 'PGanswermacros.pl' => 1,
+ 'PGauxiliaryFunctions.pl' => 1,
+ 'customizeLaTeX.pl' => 1,
+ 'PGnumericevaluators.pl' => 1,
+ 'PGfunctionevaluators.pl' => 1,
+ 'PGstringevaluators.pl' => 1,
+ 'PGmiscevaluators.pl' => 1,
+ 'PGcommonFunctions.pl' => 1
+);
my %brokenMacros = ('answerDiscussion.pl' => 1);
-opendir my $dir, "$ENV{PG_ROOT}/macros" or die "Unable to open pg macro directory: $!";
-my @macro_files = sort grep { !/^\./ && /\.pl$/ && !$baseMacros{$_} && !$brokenMacros{$_} } readdir $dir;
-closedir $dir;
+# Find all macro files inside the $ENV{PG_ROOT}/macros directory.
+my @macro_files;
+find(
+ sub {
+ # Must be a file that has the ".pl" suffix.
+ return unless -f && /\.pl$/;
+ return if $baseMacros{$_} || $brokenMacros{$_};
+ push @macro_files, $_;
+ },
+ "$ENV{PG_ROOT}/macros"
+);
+
+@macro_files = sort @macro_files;
for (@macro_files) {
subtest $_ => sub {