diff --git a/.github/workflows/check-formats.yml b/.github/workflows/check-formats.yml index 092dabe309..cef9d5140c 100644 --- a/.github/workflows/check-formats.yml +++ b/.github/workflows/check-formats.yml @@ -18,9 +18,9 @@ jobs: image: perl:5.38 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install dependencies - run: cpanm -n Perl::Tidy@20240903 + run: cpanm -n Perl::Tidy@20260204 - name: Run perltidy shell: bash run: | @@ -33,11 +33,11 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '20' + node-version: '24' - name: Install Dependencies run: cd htdocs && npm ci --ignore-scripts - name: Check formatting with prettier diff --git a/.github/workflows/generate-and-publish-docs.yml b/.github/workflows/generate-and-publish-docs.yml new file mode 100644 index 0000000000..1d9da6e715 --- /dev/null +++ b/.github/workflows/generate-and-publish-docs.yml @@ -0,0 +1,76 @@ +name: Generate and Publish PG POD and Sample Problem Documentation + +on: + # Execute the workflow anytime something is merged into or pushed to main. + push: + branches: + - main + + # This allows this workflow to be triggered manually from the actions tab. + workflow_dispatch: + +jobs: + generate-documentation: + runs-on: ubuntu-24.04 + + steps: + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends --no-install-suggests \ + pandoc \ + libmojolicious-perl \ + libpandoc-wrapper-perl \ + libpod-parser-perl + + - name: Checkout pg code + uses: actions/checkout@v6 + with: + path: pg + + - name: Create output directory + run: mkdir /home/runner/work/pg/pg/documentation + + - name: Generate sample problem documentation + run: | + perl pg/bin/parse-problem-doc.pl \ + --problem-dir=/home/runner/work/pg/pg/pg/tutorial/sample-problems \ + --out-dir=/home/runner/work/pg/pg/documentation/sampleproblems \ + --pod-base-url=/pg/pod \ + --sample-problem-base-url=/pg/sampleproblems + + - name: Generate POD + run: perl pg/bin/generate-pg-pod.pl --output-dir=documentation/pod --base-url=/pg/pod/ --home-url=/pg + + - name: Generate search data + run: perl pg/bin/generate-search-data.pl --out-file=documentation/sample-problem-search-data.json + + - name: Copy assets + run: | + cp /home/runner/work/pg/pg/pg/tutorial/templates/index.html \ + /home/runner/work/pg/pg/documentation/ + cp /home/runner/work/pg/pg/pg/htdocs/js/SampleProblemViewer/documentation-search.js \ + /home/runner/work/pg/pg/documentation/ + + - name: Upload documentation html + uses: actions/upload-pages-artifact@v3 + with: + path: documentation + + publish-documentation: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + # Set the permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages. + permissions: + pages: write + id-token: write + + runs-on: ubuntu-24.04 + needs: generate-documentation + + steps: + - name: Publish to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 587934f0a6..11713a9430 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout PG code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install Ubuntu dependencies run: | diff --git a/.gitignore b/.gitignore index b6da4a2dea..6ea6effdce 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ htdocs/static-assets.json htdocs/**/*.min.js htdocs/**/*.min.css htdocs/tmp + +sagecell-docker/sagecell-docker.service diff --git a/LICENSE b/LICENSE index f079e7c087..8ebfd9c1ce 100644 --- a/LICENSE +++ b/LICENSE @@ -1,8 +1,8 @@ - WeBWorK - Online Homework Delivery System + WeBWorK PG Version 2.* - Copyright 2000-2025, The WeBWorK Project + Copyright 2000-2026, The WeBWorK Project + All rights reserved. This program is free software; you can redistribute it and/or modify @@ -12,19 +12,19 @@ Software Foundation; either version 2, or (at your option) any later version, or - b) the "Artistic License" which comes with this package. + b) the "Artistic License" 1.0 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. - You should have received a copy of the Artistic License with this - package, in the file named "Artistic". If not, we'll be glad to provide - one. + You should have received a copy of the Artistic License 1.0 with this + package, in the file named "Artistic" inside the `doc` folder. If not, + you can find a copy at https://github.com/openwebwork/pg/blob/main/doc/Artistic + or https://perlfoundation.org/artistic-license-10.html. You should also have received a copy of the GNU General Public License - along with this program in the file named "Copying". If not, write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - 02111-1307, USA or visit their web page on the internet at - http://www.gnu.org/copyleft/gpl.html. + along with this program in the file named "Copying" inside the `doc` folder. + If not, you can find a copy at https://github.com/openwebwork/pg/blob/main/doc/Copying + or https://www.gnu.org/licenses/old-licenses/gpl-2.0.html. diff --git a/README b/README deleted file mode 100644 index 9a8d30bb13..0000000000 --- a/README +++ /dev/null @@ -1,11 +0,0 @@ - WeBWorK - Program Generation Language - Version 2.* - Branch: https://github.com/openwebwork - - - http://webwork.maa.org/wiki/Category:Release_Notes - - Copyright 2000-2025, The WeBWorK Project - http://webwork.maa.org - All rights reserved. diff --git a/README.md b/README.md index d40515112f..ddfa947f8a 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,20 @@ # Welcome to WeBWorK -![main workflow](https://github.com/pstaabp/pg/actions/workflows/coverage.yml/badge.svg) -[![codecov](https://codecov.io/gh/pstaabp/pg/branch/unit-test/graph/badge.svg?token=H7WYHBDB9S)](https://codecov.io/gh/pstaabp/pg) -![GitHub last commit](https://img.shields.io/github/last-commit/pstaabp/pg/unit-test) - -WeBWorK is an open-source online homework system for math and sciences courses. WeBWorK is supported by the MAA and the NSF and comes with an Open Problem Library (OPL) of over 30,000 homework problems. Problems in the OPL target most lower division undergraduate math courses and some advanced courses. Supported courses include college algebra, discrete mathematics, probability and statistics, single and multivariable calculus, differential equations, linear algebra and complex analysis. Find out more at the main WeBWorK [webpage](http://webwork.maa.org). +WeBWorK is an open-source online homework system for math and sciences courses. WeBWorK is supported by the MAA and the NSF and comes with an Open Problem Library (OPL) of over 30,000 homework problems. Problems in the OPL target most lower division undergraduate math courses and some advanced courses. Supported courses include college algebra, discrete mathematics, probability and statistics, single and multivariable calculus, differential equations, linear algebra and complex analysis. Find out more at the main WeBWorK [webpage](https://openwebwork.org). ## Information for Users New users interested in getting started with their own WeBWorK server, or instructors looking to learn more about how to use WeBWorK in their classes, should take a look at one of the following resources: -* [WeBWorK wiki](http://webwork.maa.org/wiki/Main_Page) - The main WeBWorK wiki - * [Instructors](http://webwork.maa.org/wiki/Instructors) - Information for Instructors - * [Problem Authors](http://webwork.maa.org/wiki/Authors) - Information for Problem Authors -* [WW_Install](http://github.com/aubreyja/ww_install) - Information for using the WW_install script -* [Forum](http://webwork.maa.org/moodle/mod/forum/index.php?id=3) - The WeBWorK Forum +* [WeBWorK wiki](https://wiki.openwebwork.org/wiki/WeBWorK_Main_Page) - The main WeBWorK wiki + * [Instructors](https://wiki.openwebwork.org/wiki/Instructors) - Information for Instructors + * [Problem Authors](https://wiki.openwebwork.org/wiki/Authors) - Information for Problem Authors +* [Forum](https://forums.openwebwork.org/mod/forum/index.php?id=3) - The WeBWorK Forum * [Frequently Asked Questions](https://github.com/openwebwork/webwork2/wiki/Frequently-Asked-Questions) - A list of frequently asked questions. ## Information For Developers -People interested in developing new features for WeBWorK should take a look at the following resources. People interested in developing new problems for WeBWorK should visit [Problem Authors](http://webwork.maa.org/wiki/Authors). +People interested in developing new features for WeBWorK should take a look at the following resources. People interested in developing new problems for WeBWorK should visit [Problem Authors](https://wiki.openwebwork.org/wiki/Authors). * [First Time Setup](https://github.com/openwebwork/webwork2/wiki/First-Time-Setup) - Setting up your clone of this github repo for the first time. * [Coding and Workflow](https://github.com/openwebwork/webwork2/wiki/Coding-and-Workflow) - Our suggested workflow processes. Following this will make it much easier to get code accepted into the repo. diff --git a/VERSION b/VERSION index fd27d43097..edccfa858a 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ -$PG_VERSION ='2.20'; -$PG_COPYRIGHT_YEARS = '1996-2025'; +$PG_VERSION ='2.21'; +$PG_COPYRIGHT_YEARS = '1996-2026'; 1; diff --git a/assets/pod-templates/category-index.mt b/assets/pod-templates/category-index.mt new file mode 100644 index 0000000000..61fe3b450c --- /dev/null +++ b/assets/pod-templates/category-index.mt @@ -0,0 +1,94 @@ + + +% + + + + <%= $title %> + + + + + +% + + + % + % my ($index, $macro_index, $content, $macro_content) = ('', '', '', ''); + % for my $macro (@$macros_order) { + % my $new_index = begin + <%= $macro_names->{$macro} // $macro %> + % end + % $macro_index .= $new_index->(); + % my $new_content = begin +

<%= $macro_names->{$macro} // $macro %>

+
+ % for my $file (sort { $a->[1] cmp $b->[1] } @{ $macros->{$macro} }) { + <%= $file->[1] %> + % } +
+ % end + % $macro_content .= $new_content->(); + % } + % for my $section (@$section_order) { + % next unless defined $pod_index->{$section}; + % my $new_index = begin + <%= $sections->{$section} %> + % if ($section eq 'macros') { + + % } + % end + % $index .= $new_index->(); + % my $new_content = begin +

<%= $sections->{$section} %>

+
+ % if ($section eq 'macros') { + <%= $macro_content =%> + % } else { + % for my $file (sort { $a->[1] cmp $b->[1] } @{ $pod_index->{$section} }) { + + <%= $file->[1] %> + + % } + % } +
+ % end + % $content .= $new_content->(); + % } + % + +
+
+ <%= $content =%> +

Generated <%= $date %>

+
+
+ +% + diff --git a/assets/pod-templates/pod.mt b/assets/pod-templates/pod.mt new file mode 100644 index 0000000000..53cc4a4898 --- /dev/null +++ b/assets/pod-templates/pod.mt @@ -0,0 +1,55 @@ + + +% + + + + <%= $title %> + + + + + +% + + + +
+
+ <%= $content =%> +
+
+ +% + diff --git a/assets/stop-words-en.txt b/assets/stop-words-en.txt new file mode 100644 index 0000000000..cc09be2ec7 --- /dev/null +++ b/assets/stop-words-en.txt @@ -0,0 +1,1320 @@ +# Stop words from https://github.com/Alir3z4/stop-words. + +'ll +'tis +'twas +'ve +a +a's +able +ableabout +about +above +abroad +abst +accordance +according +accordingly +across +act +actually +ad +added +adj +adopted +ae +af +affected +affecting +affects +after +afterwards +ag +again +against +ago +ah +ahead +ai +ain't +aint +al +all +allow +allows +almost +alone +along +alongside +already +also +although +always +am +amid +amidst +among +amongst +amoungst +amount +an +and +announce +another +any +anybody +anyhow +anymore +anyone +anything +anyway +anyways +anywhere +ao +apart +apparently +appear +appreciate +appropriate +approximately +aq +ar +are +area +areas +aren +aren't +arent +arise +around +arpa +as +aside +ask +asked +asking +asks +associated +at +au +auth +available +aw +away +awfully +az +b +ba +back +backed +backing +backs +backward +backwards +bb +bd +be +became +because +become +becomes +becoming +been +before +beforehand +began +begin +beginning +beginnings +begins +behind +being +beings +believe +below +beside +besides +best +better +between +beyond +bf +bg +bh +bi +big +bill +billion +biol +bj +bm +bn +bo +both +bottom +br +brief +briefly +bs +bt +but +buy +bv +bw +by +bz +c +c'mon +c's +ca +call +came +can +can't +cannot +cant +caption +case +cases +cause +causes +cc +cd +certain +certainly +cf +cg +ch +changes +ci +ck +cl +clear +clearly +click +cm +cmon +cn +co +co. +com +come +comes +computer +con +concerning +consequently +consider +considering +contain +containing +contains +copy +corresponding +could +could've +couldn +couldn't +couldnt +course +cr +cry +cs +cu +currently +cv +cx +cy +cz +d +dare +daren't +darent +date +de +dear +definitely +describe +described +despite +detail +did +didn +didn't +didnt +differ +different +differently +directly +dj +dk +dm +do +does +doesn +doesn't +doesnt +doing +don +don't +done +dont +doubtful +down +downed +downing +downs +downwards +due +during +dz +e +each +early +ec +ed +edu +ee +effect +eg +eh +eight +eighty +either +eleven +else +elsewhere +empty +end +ended +ending +ends +enough +entirely +er +es +especially +et +et-al +etc +even +evenly +ever +evermore +every +everybody +everyone +everything +everywhere +ex +exactly +example +except +f +face +faces +fact +facts +fairly +far +farther +felt +few +fewer +ff +fi +fifteen +fifth +fifty +fify +fill +find +finds +fire +first +five +fix +fj +fk +fm +fo +followed +following +follows +for +forever +former +formerly +forth +forty +forward +found +four +fr +free +from +front +full +fully +further +furthered +furthering +furthermore +furthers +fx +g +ga +gave +gb +gd +ge +general +generally +get +gets +getting +gf +gg +gh +gi +give +given +gives +giving +gl +gm +gmt +gn +go +goes +going +gone +good +goods +got +gotten +gov +gp +gq +gr +great +greater +greatest +greetings +group +grouped +grouping +groups +gs +gt +gu +gw +gy +h +had +hadn't +hadnt +half +happens +hardly +has +hasn +hasn't +hasnt +have +haven +haven't +havent +having +he +he'd +he'll +he's +hed +hell +hello +help +hence +her +here +here's +hereafter +hereby +herein +heres +hereupon +hers +herself +herse” +hes +hi +hid +high +higher +highest +him +himself +himse” +his +hither +hk +hm +hn +home +homepage +hopefully +how +how'd +how'll +how's +howbeit +however +hr +ht +htm +html +http +hu +hundred +i +i'd +i'll +i'm +i've +i.e. +id +ie +if +ignored +ii +il +ill +im +immediate +immediately +importance +important +in +inasmuch +inc +inc. +indeed +index +indicate +indicated +indicates +information +inner +inside +insofar +instead +int +interest +interested +interesting +interests +into +invention +inward +io +iq +ir +is +isn +isn't +isnt +it +it'd +it'll +it's +itd +itll +its +itself +itse” +ive +j +je +jm +jo +join +jp +just +k +ke +keep +keeps +kept +keys +kg +kh +ki +kind +km +kn +knew +know +known +knows +kp +kr +kw +ky +kz +l +la +large +largely +last +lately +later +latest +latter +latterly +lb +lc +least +length +less +lest +let +let's +lets +li +like +liked +likely +likewise +line +little +lk +ll +long +longer +longest +look +looking +looks +low +lower +lr +ls +lt +ltd +lu +lv +ly +m +ma +made +mainly +make +makes +making +man +many +may +maybe +mayn't +maynt +mc +md +me +mean +means +meantime +meanwhile +member +members +men +merely +mg +mh +microsoft +might +might've +mightn't +mightnt +mil +mill +million +mine +minus +miss +mk +ml +mm +mn +mo +more +moreover +most +mostly +move +mp +mq +mr +mrs +ms +msie +mt +mu +much +mug +must +must've +mustn't +mustnt +mv +mw +mx +my +myself +myse” +mz +n +na +name +namely +nay +nc +nd +ne +near +nearly +necessarily +necessary +need +needed +needing +needn't +neednt +needs +neither +net +netscape +never +neverf +neverless +nevertheless +new +newer +newest +next +nf +ng +ni +nine +ninety +nl +no +no-one +nobody +non +none +nonetheless +noone +nor +normally +nos +not +noted +nothing +notwithstanding +novel +now +nowhere +np +nr +nu +null +number +numbers +nz +o +obtain +obtained +obviously +of +off +often +oh +ok +okay +old +older +oldest +om +omitted +on +once +one +one's +ones +only +onto +open +opened +opening +opens +opposite +or +ord +order +ordered +ordering +orders +org +other +others +otherwise +ought +oughtn't +oughtnt +our +ours +ourselves +out +outside +over +overall +owing +own +p +pa +page +pages +part +parted +particular +particularly +parting +parts +past +pe +per +perhaps +pf +pg +ph +pk +pl +place +placed +places +please +plus +pm +pmid +pn +point +pointed +pointing +points +poorly +possible +possibly +potentially +pp +pr +predominantly +present +presented +presenting +presents +presumably +previously +primarily +probably +problem +problems +promptly +proud +provided +provides +pt +put +puts +pw +py +q +qa +que +quickly +quite +qv +r +ran +rather +rd +re +readily +really +reasonably +recent +recently +ref +refs +regarding +regardless +regards +related +relatively +research +reserved +respectively +resulted +resulting +results +right +ring +ro +room +rooms +round +ru +run +rw +s +sa +said +same +saw +say +saying +says +sb +sc +sd +se +sec +second +secondly +seconds +section +see +seeing +seem +seemed +seeming +seems +seen +sees +self +selves +sensible +sent +serious +seriously +seven +seventy +several +sg +sh +shall +shan't +shant +she +she'd +she'll +she's +shed +shell +shes +should +should've +shouldn +shouldn't +shouldnt +show +showed +showing +shown +showns +shows +si +side +sides +significant +significantly +similar +similarly +since +sincere +site +six +sixty +sj +sk +sl +slightly +sm +small +smaller +smallest +sn +so +some +somebody +someday +somehow +someone +somethan +something +sometime +sometimes +somewhat +somewhere +soon +sorry +specifically +specified +specify +specifying +sr +st +state +states +still +stop +strongly +su +sub +substantially +successfully +such +sufficiently +suggest +sup +sure +sv +sy +system +sz +t +t's +take +taken +taking +tc +td +tell +ten +tends +test +text +tf +tg +th +than +thank +thanks +thanx +that +that'll +that's +that've +thatll +thats +thatve +the +their +theirs +them +themselves +then +thence +there +there'd +there'll +there're +there's +there've +thereafter +thereby +thered +therefore +therein +therell +thereof +therere +theres +thereto +thereupon +thereve +these +they +they'd +they'll +they're +they've +theyd +theyll +theyre +theyve +thick +thin +thing +things +think +thinks +third +thirty +this +thorough +thoroughly +those +thou +though +thoughh +thought +thoughts +thousand +three +throug +through +throughout +thru +thus +til +till +tip +tis +tj +tk +tm +tn +to +today +together +too +took +top +toward +towards +tp +tr +tried +tries +trillion +truly +try +trying +ts +tt +turn +turned +turning +turns +tv +tw +twas +twelve +twenty +twice +two +tz +u +ua +ug +uk +um +un +under +underneath +undoing +unfortunately +unless +unlike +unlikely +until +unto +up +upon +ups +upwards +us +use +used +useful +usefully +usefulness +uses +using +usually +uucp +uy +uz +v +va +value +various +vc +ve +versus +very +vg +vi +via +viz +vn +vol +vols +vs +vu +w +want +wanted +wanting +wants +was +wasn +wasn't +wasnt +way +ways +we +we'd +we'll +we're +we've +web +webpage +website +wed +welcome +well +wells +went +were +weren +weren't +werent +weve +wf +what +what'd +what'll +what's +what've +whatever +whatll +whats +whatve +when +when'd +when'll +when's +whence +whenever +where +where'd +where'll +where's +whereafter +whereas +whereby +wherein +wheres +whereupon +wherever +whether +which +whichever +while +whilst +whim +whither +who +who'd +who'll +who's +whod +whoever +whole +wholl +whom +whomever +whos +whose +why +why'd +why'll +why's +widely +width +will +willing +wish +with +within +without +won +won't +wonder +wont +words +work +worked +working +works +world +would +would've +wouldn +wouldn't +wouldnt +ws +www +x +y +ye +year +years +yes +yet +you +you'd +you'll +you're +you've +youd +youll +young +younger +youngest +your +youre +yours +yourself +yourselves +youve +yt +yu +z +za +zero +zm +zr + +# Additional specific stop words specific to POD and sample problem documentation. +constructor +description +error +errors +macro +macros +pod +podlink +problink +synopsis +usage +funciton +functions +method +methods +option +options +todo +fixme +_ diff --git a/assets/tex/CAPA.tex b/assets/tex/CAPA.tex deleted file mode 100644 index 61d1ffa208..0000000000 --- a/assets/tex/CAPA.tex +++ /dev/null @@ -1,15 +0,0 @@ -% capa tex macros - -\newcommand{\capa}{{\sl C\kern-.10em\raise-.00ex\hbox{\rm A}\kern-.22em% -{\sl P}\kern-.14em\kern-.01em{\rm A}}} - -\newenvironment{choicelist} -{\begin{list}{} - {\setlength{\rightmargin}{0in}\setlength{\leftmargin}{0.13in} - \setlength{\topsep}{0.05in}\setlength{\itemsep}{0.022in} - \setlength{\parsep}{0in}\setlength{\belowdisplayskip}{0.04in} - \setlength{\abovedisplayskip}{0.05in} - \setlength{\abovedisplayshortskip}{-0.04in} - \setlength{\belowdisplayshortskip}{0.04in}} - } -{\end{list}} diff --git a/assets/tex/pg.sty b/assets/tex/pg.sty index 7d8ce2c51b..74ad8e85ec 100644 --- a/assets/tex/pg.sty +++ b/assets/tex/pg.sty @@ -8,6 +8,7 @@ \usepackage[table]{xcolor} % niceTables.pl \usepackage{multicol} % DragNDrop.pm \usepackage[version=4]{mhchem} % chemistry macros +\usepackage{cancel} % cancel strikethrough macros % some packages need to be handled differently for xelatex vs pdflatex \usepackage{iftex} @@ -22,11 +23,10 @@ \fi % PG macro collections -\input{CAPA.tex} \input{PGML.tex} % The macro alternatives for < should be used in math in PG problems to help -% avoid issus with a bare < in HMTL or XML output. The alternatives for > are +% avoid issues with a bare < in HTML or XML output. The alternatives for > are % provided for parity. \newcommand{\lt}{<} \newcommand{\gt}{>} @@ -36,7 +36,7 @@ % semantic macro definitions used by PG \newcommand{\answerRule}[2][]{\raisebox{-3pt}{\parbox[t]{#2ex}{\hrulefill}}} -% height of a strut, used for example to possition the top border of an inline image +% height of a strut, used for example to position the top border of an inline image % unit is initialized here, but value is set locally where needed \newlength{\strutheight} diff --git a/bin/generate-pg-pod.pl b/bin/generate-pg-pod.pl new file mode 100755 index 0000000000..98f73a3458 --- /dev/null +++ b/bin/generate-pg-pod.pl @@ -0,0 +1,80 @@ +#!/usr/bin/env perl + +=head1 NAME + +generate-pg-pod.pl - Convert PG POD into HTML form. + +=head1 SYNOPSIS + +generate-pg-pod.pl [options] + + Options: + -o|--output-dir Directory to save the output files to. (required) + -b|--base-url Base url location used on server. (default: /) + This is needed for internal POD links to work correctly. + -h|--home-url Home page url on the server. (default: /) + -v|--verbose Increase the verbosity of the output. + (Use multiple times for more verbosity.) + +=head1 DESCRIPTION + +Convert PG POD into HTML form. + +=cut + +use strict; +use warnings; + +use Getopt::Long qw(:config bundling); +use Pod::Usage; + +my ($output_dir, $base_url, $home_url); +my $verbose = 0; +GetOptions( + 'o|output-dir=s' => \$output_dir, + 'b|base-url=s' => \$base_url, + 'h|home-url=s' => \$home_url, + 'v|verbose+' => \$verbose +); + +pod2usage(2) unless $output_dir; + +$base_url = "/" if !$base_url; +$home_url = "/" if !$home_url; + +use Mojo::Template; +use IO::File; +use File::Copy; +use File::Path qw(make_path remove_tree); +use File::Basename qw(dirname); +use Cwd qw(abs_path); + +use lib abs_path(dirname(dirname(__FILE__))) . '/lib'; + +use WeBWorK::Utils::PODtoHTML; + +my $pg_root = abs_path(dirname(dirname(__FILE__))); + +print "Reading: $pg_root\n" if $verbose; + +remove_tree($output_dir); +make_path($output_dir); + +my $htmldocs = WeBWorK::Utils::PODtoHTML->new( + source_root => $pg_root, + dest_root => $output_dir, + template_dir => "$pg_root/assets/pod-templates", + dest_url => $base_url, + home_url => $home_url, + home_url_link_name => 'PG Documentation Home', + verbose => $verbose +); +$htmldocs->convert_pods; + +make_path("$output_dir/assets"); +copy("$pg_root/htdocs/js/PODViewer/podviewer.css", "$output_dir/assets/podviewer.css"); +print "copying $pg_root/htdocs/js/PODViewer/podviewer.css to $output_dir/assets/podviewer.css\n" if $verbose; +copy("$pg_root/htdocs/js/PODViewer/podviewer.js", "$output_dir/assets/podviewer.js"); +print "copying $pg_root/htdocs/js/PODViewer/podviewer.css to $output_dir/assets/podviewer.js\n" if $verbose; + +1; diff --git a/bin/generate-search-data.pl b/bin/generate-search-data.pl new file mode 100755 index 0000000000..c3ab227eec --- /dev/null +++ b/bin/generate-search-data.pl @@ -0,0 +1,42 @@ +#!/usr/bin/env perl + +=head1 NAME + +generate-search-data.pl - Generate search data for macro and sample problem +documentation. + +=head1 SYNOPSIS + +generate-search-data.pl [options] + + Options: + -o|--out-file File to save the search data to. (required) + +=head1 DESCRIPTION + +Generate search data for macro and sample problem documentation. + +=cut + +use strict; +use warnings; + +my $pgRoot; + +use Mojo::File qw(curfile); +BEGIN { $pgRoot = curfile->dirname->dirname; } + +use lib "$pgRoot/lib"; + +use Getopt::Long; +use Pod::Usage; + +use WeBWorK::PG::SampleProblemParser qw(getSearchData); + +my $outFile; +GetOptions("o|out-file=s" => \$outFile); +pod2usage(2) unless $outFile; + +getSearchData($outFile); + +1; diff --git a/bin/parse-problem-doc.pl b/bin/parse-problem-doc.pl index 84f9a303c7..818416093d 100755 --- a/bin/parse-problem-doc.pl +++ b/bin/parse-problem-doc.pl @@ -1,5 +1,29 @@ #!/usr/bin/env perl +=head1 NAME + +parse-problem-doc.pl - Parse sample problem documentation. + +=head1 SYNOPSIS + +parse-problem-doc.pl [options] + + Options: + -d|--problem-dir Directory containing sample problems to be parsed. + This defaults to the tutorial/sample-problems directory + in the PG root directory if not given. + -o|--out-dir Directory to save the output files to. (required) + -p|--pod-base-url Base URL location for POD on server. (required) + -s|--sample-problem-base-url + Base URL location for sample problems on server. (required) + -v|--verbose Give verbose feedback. + +=head1 DESCRIPTION + +Parse sample problem documentation. + +=cut + use strict; use warnings; use experimental 'signatures'; @@ -17,26 +41,26 @@ BEGIN use Mojo::Template; use File::Basename qw(basename); use Getopt::Long; +use Pod::Usage; use File::Copy qw(copy); use Pod::Simple::Search; -use SampleProblemParser qw(parseSampleProblem generateMetadata); +use WeBWorK::PG::SampleProblemParser qw(parseSampleProblem generateMetadata); my $problem_dir = "$pg_root/tutorial/sample-problems"; -my ($out_dir, $pod_root, $pg_doc_home); +my ($out_dir, $pod_base_url, $sample_problem_base_url); my $verbose = 0; GetOptions( - "d|problem_dir=s" => \$problem_dir, - "o|out_dir=s" => \$out_dir, - "v|verbose" => \$verbose, - "p|pod_root=s" => \$pod_root, - "h|pg_doc_home=s" => \$pg_doc_home, + "d|problem-dir=s" => \$problem_dir, + "o|out-dir=s" => \$out_dir, + "p|pod-base-url=s" => \$pod_base_url, + "s|sample-problem-base-url=s" => \$sample_problem_base_url, + "v|verbose" => \$verbose ); -die "out_dir, pod_root, and pg_doc_home must be provided.\n" - unless $out_dir && $pod_root && $pg_doc_home; +pod2usage(2) unless $out_dir && $pod_base_url && $sample_problem_base_url; my $mt = Mojo::Template->new(vars => 1); my $template_dir = "$pg_root/tutorial/templates"; @@ -46,7 +70,7 @@ BEGIN my @problem_types = qw(sample technique snippet); -$pod_root .= '/pg/macros'; +$pod_base_url .= '/macros'; mkdir $out_dir unless -d $out_dir; # Build a hash of all PG files for linking. @@ -55,16 +79,16 @@ BEGIN for (keys %$index_table) { renderSampleProblem( $_ =~ s/.pg$//r, - metadata => $index_table, - macro_locations => $macro_locations, - pod_root => $pod_root, - pg_doc_home => $pg_doc_home, - url_extension => '.html', - problem_dir => $problem_dir, - out_dir => $out_dir, - template_dir => $template_dir, - mt => $mt, - verbose => $verbose + metadata => $index_table, + macro_locations => $macro_locations, + pod_base_url => $pod_base_url, + sample_problem_base_url => $sample_problem_base_url, + url_extension => '.html', + problem_dir => $problem_dir, + out_dir => $out_dir, + template_dir => $template_dir, + mt => $mt, + verbose => $verbose ); } @@ -92,7 +116,7 @@ ($filename, %global) return; } -# Ouput index files. +# Output index files. for (qw(categories subjects macros techniques)) { my $options = { metadata => $index_table, diff --git a/bin/perltidy-pg.pl b/bin/perltidy-pg.pl index b51840b017..224d66d3bc 100755 --- a/bin/perltidy-pg.pl +++ b/bin/perltidy-pg.pl @@ -19,7 +19,7 @@ =head1 OPTIONS Note that if the -pro=file option is not given, then this script will attempt to use the perltidy-pg.rc file in the PG bin directory for this option. For this to -work the the perltidy-pg.rc file in the PG bin directory must be readable. +work the perltidy-pg.rc file in the PG bin directory must be readable. =cut diff --git a/bin/pg-critic.pl b/bin/pg-critic.pl new file mode 100755 index 0000000000..60ec98cf48 --- /dev/null +++ b/bin/pg-critic.pl @@ -0,0 +1,140 @@ +#!/usr/bin/env perl + +=head1 NAME + +pg-critic.pl - Command line interface to critique PG problem code. + +=head1 SYNOPSIS + + pg-critic.pl [options] file1 file2 ... + +Options: + + -f|--format Format of the output, either 'text' or 'json'. + 'text' is the default and will output a plain text + listing of the results. 'json' will output results in + JavaScript Object Notation. + -o|--output-file Filename to write output to. If not provided output will + be printed to STDOUT. + -n|--no-details Only show the filename and badness score and do not + include the details in the output for each file. + -s|--strict Disable "## no critic" annotations and force all + policies to be enforced. + -p|--pg-only Only include PG critic policy violations and ignore + general Perl critic policy violations (both for the + score and display). + -h|--help Show the help message. + +=head1 DESCRIPTION + +C is a PG problem source code analyzer. It is the executable +front-end to the L module, which attempts to identify usage +of old or deprecated PG features and code that does not conform to current +best-practices. + +=cut + +use Mojo::Base -signatures; + +use Mojo::File qw(curfile path); +use Mojo::JSON qw(encode_json); +use Getopt::Long; +use Pod::Usage; + +use lib curfile->dirname->dirname . '/lib'; + +use WeBWorK::PG::Critic qw(critiquePGFile); + +GetOptions( + 'f|format=s' => \my $format, + 'o|output-file=s' => \my $filename, + 'n|no-details' => \my $noDetails, + 's|strict' => \my $force, + 'p|pg-only' => \my $pgOnly, + 'h|help' => \my $show_help +); +pod2usage(2) if $show_help; + +$format //= 'text'; + +$format = lc($format); + +unless (@ARGV) { + say 'A list of pg problem files must be provided.'; + pod2usage(2); +} +unless ($format eq 'text' || $format eq 'json') { + say 'The output format must be "text" or "json"'; + pod2usage(2); +} + +sub scoreProblem (@violations) { + my $score = 0; + for (@violations) { + if ($_->policy =~ /^Perl::Critic::Policy::PG::/) { + $score += $_->explanation->{score} // 0; + } else { + # Add 5 points for any of the default Perl::Critic::Policy violations. + # These will not have a score in the explanation. + $score += 5; + } + } + return $score; +} + +my @results; + +for (@ARGV) { + my @violations = critiquePGFile($_, $force); + @violations = grep { $_->policy =~ /^Perl::Critic::Policy::PG::/ } @violations if $pgOnly; + + my (@pgCriticViolations, @perlCriticViolations); + if (!$noDetails) { + @pgCriticViolations = grep { $_->policy =~ /^Perl::Critic::Policy::PG::/ } @violations; + @perlCriticViolations = grep { $_->policy !~ /^Perl::Critic::Policy::PG::/ } @violations; + } + + push( + @results, + { + file => $_, + score => scoreProblem(@violations), + $noDetails + ? () + : ( + @pgCriticViolations ? (pgCriticViolations => \@pgCriticViolations) : (), + @perlCriticViolations ? (perlCriticViolations => \@perlCriticViolations) : () + ) + } + ); +} + +Perl::Critic::Violation::set_format('%m at line %l, column %c. (%p)'); + +my $outputMethod = $format eq 'json' ? \&encode_json : sub { + my $results = shift; + + return join( + "\n", + map { ( + "Filename: $_->{file}", + "Score: $_->{score}", + @{ $_->{pgCriticViolations} // [] } + ? ('PG critic violations:', map { "\t" . $_->to_string } @{ $_->{pgCriticViolations} }) + : (), + @{ $_->{perlCriticViolations} // [] } + ? ('Perl critic violations:', map { "\t" . $_->to_string } @{ $_->{perlCriticViolations} }) + : () + ) } @$results + ); +}; + +if ($filename) { + eval { path($filename)->spew($outputMethod->(\@results), 'UTF-8') }; + if ($@) { say "Unable to write results to $filename: $@"; } + else { say "Results written in $format format to $filename"; } +} else { + say $outputMethod->(\@results); +} + +1; diff --git a/bin/run-perltidy.pl b/bin/run-perltidy.pl index 9f2eeff8cb..c6a9728c11 100755 --- a/bin/run-perltidy.pl +++ b/bin/run-perltidy.pl @@ -14,8 +14,8 @@ =head1 DESCRIPTION =head1 OPTIONS -For this script to work the the .perltidyrc file in the pg root directory -must be readable. Note that the pg root directory is automatically detected. +For this script to work the .perltidyrc file in the pg root directory must be +readable. Note that the pg root directory is automatically detected. This script accepts all of the options that are accepted by perltidy. See the perltidy documentation for details. @@ -48,8 +48,8 @@ =head1 OPTIONS my $pg_root = curfile->dirname->dirname; -die "Version 20240903 of perltidy is required for this script.\nThe installed version is $Perl::Tidy::VERSION.\n" - unless $Perl::Tidy::VERSION == 20240903; +die "Version 20260204 of perltidy is required for this script.\nThe installed version is $Perl::Tidy::VERSION.\n" + unless $Perl::Tidy::VERSION == 20260204; die "The .perltidyrc file in the pg root directory is not readable.\n" unless -r "$pg_root/.perltidyrc"; @@ -64,10 +64,10 @@ =head1 OPTIONS # Validate options that were passed. my %options; my $err = Perl::Tidy::perltidy(argv => \@args, dump_options => \%options); -exit $err if $err; -die "The -pro option is not suppored by this script.\n" if defined $options{profile}; +exit $err if $err; +die "The -pro option is not supported by this script.\n" if defined $options{profile}; -unshift(@args, '-bext=/') unless defined $options{'backup-file-extension'}; +unshift(@args, '-bext=/tidybak') unless defined $options{'backup-file-extension'}; if (@files) { for (@files) { diff --git a/bin/update-localization-files b/bin/update-localization-files index 50da33991a..303520c3ea 100755 --- a/bin/update-localization-files +++ b/bin/update-localization-files @@ -6,7 +6,7 @@ function print_help_exit printf " Update the pg.pot and language .po files with translation strings from the code.\n" >&2 printf " options:\n" >&2 printf " -p|--po-update Update po files as well. By default only the pg.pot file is updated.\n" >&2 - printf " -l|--langauge Update the only given language in addition to updating the pg.pot file.\n" >&2 + printf " -l|--language Update the only given language in addition to updating the pg.pot file.\n" >&2 printf " -h|--help Show this help.\n" >&2 exit 1 } @@ -45,7 +45,7 @@ if [ -z "$PG_ROOT" ]; then fi command -v xgettext.pl >/dev/null 2>&1 || { - echo >&2 "xgettext.pl needs to be installed. It is inlcuded in the perl package Locale::Maketext::Extract. Aborting."; + echo >&2 "xgettext.pl needs to be installed. It is included in the perl package Locale::Maketext::Extract. Aborting."; exit 1; } diff --git a/conf/pg_config.dist.yml b/conf/pg_config.dist.yml index 3e4c9ab080..c2924013f9 100644 --- a/conf/pg_config.dist.yml +++ b/conf/pg_config.dist.yml @@ -18,7 +18,7 @@ directories: OPL: /opt/webwork/libraries/webwork-open-problem-library/OpenProblemLibrary Contrib: /opt/webwork/libraries/webwork-open-problem-library/Contrib - # The root PG location. This will be set from the PG_ROOT environment variable, but can be overriden here. + # The root PG location. This will be set from the PG_ROOT environment variable, but can be overridden here. root: $pg_root # Global temporary directory. This location must be writable. @@ -42,7 +42,6 @@ directories: - . - $pg_root/macros - $pg_root/macros/answers - - $pg_root/macros/capa - $pg_root/macros/contexts - $pg_root/macros/core - $pg_root/macros/graph @@ -75,6 +74,17 @@ URLs: - . - $pg_root_url/images + # By default the AskSage method uses the public sagecell service located at + # https://sagecell.sagemath.org/service. However, the server at that address + # is no longer accepting public requests. So in order for problems that use + # the AskSage method to work a different sagecell server must be used. + # Uncomment the line below and set the value to a custom sagecell server + # address to use with the AskSage method. If using the docker container built + # with the instructions and files in the sagecell-docker directory in this + # repository, the use the line below as is. + + #sagecellService: http://localhost:3500/service + # Flat-file database used to protect against MD5 hash collisions. TeX equations # are hashed to determine the name of the image file. There is a tiny chance of # a collision between two TeX strings. This file allows for that. However, this @@ -141,16 +151,10 @@ specialPGEnvironmentVars: # To enable Rserve (the R statistical server), uncomment the following two # lines. The R server needs to be installed and running in order for this to - # work. See http://webwork.maa.org/wiki/R_in_WeBWorK for more info. + # work. See https://wiki.openwebwork.org/wiki/R_in_WeBWorK for more info. #Rserve: # host: localhost - # Locations of CAPA resources. (Only necessary if you need to use converted CAPA problems.) - CAPA_Tools: $Contrib_dir/CAPA/macros/CAPA_Tools/ - CAPA_MCTools: $Contrib_dir/Contrib/CAPA/macros/CAPA_MCTools/ - CAPA_GraphicsDirectory: $Contrib_dir/Contrib/CAPA/CAPA_Graphics/ - CAPA_Graphics_URL: $pg_root_url/CAPA_Graphics/ - # Answer evaluatior defaults ansEvalDefaults: functAbsTolDefault: 0.001 @@ -205,7 +209,7 @@ displayModeOptions: # PG modules to load # The first item of each list is the module file to load. The remaining items are additional packages to import that are # also contained in that file. -# That is, if you wish to include a file MyModule.pm which containes the package MyModule and the additional packages +# That is, if you wish to include a file MyModule.pm which contains the package MyModule and the additional packages # Dependency1 and Dependency2, then these should appear as [Mymodule, Dependency1, Dependency2]. modules: - [Encode] @@ -236,7 +240,7 @@ modules: - [Multiple] - [PGrandom] - [Regression] - - ['Plots::Plot', 'Plots::Axes', 'Plots::Data', 'Plots::Tikz', 'Plots::JSXGraph', 'Plots::GD'] + - ['Plots::Plot', 'Plots::Axes', 'Plots::Data', 'Plots::Tikz', 'Plots::JSXGraph'] - [Select] - [Units] - [VectorField] diff --git a/cpanfile b/cpanfile index 12c7e39b75..00b469c470 100644 --- a/cpanfile +++ b/cpanfile @@ -24,6 +24,12 @@ on runtime => sub { # Needed for Rserve recommends 'IO::Handle'; + + # Needed for WeBWorK::PG::Tidy + recommends 'Perl::Tidy'; + + # Needed for WeBWorK::PG::PGProblemCritic + recommends 'Perl::Critic'; }; on test => sub { diff --git a/doc/Artistic b/doc/Artistic new file mode 100644 index 0000000000..5f221241e8 --- /dev/null +++ b/doc/Artistic @@ -0,0 +1,131 @@ + + + + + The "Artistic License" + + Preamble + +The intent of this document is to state the conditions under which a +Package may be copied, such that the Copyright Holder maintains some +semblance of artistic control over the development of the package, +while giving the users of the package the right to use and distribute +the Package in a more-or-less customary fashion, plus the right to make +reasonable modifications. + +Definitions: + + "Package" refers to the collection of files distributed by the + Copyright Holder, and derivatives of that collection of files + created through textual modification. + + "Standard Version" refers to such a Package if it has not been + modified, or has been modified in accordance with the wishes + of the Copyright Holder as specified below. + + "Copyright Holder" is whoever is named in the copyright or + copyrights for the package. + + "You" is you, if you're thinking about copying or distributing + this Package. + + "Reasonable copying fee" is whatever you can justify on the + basis of media cost, duplication charges, time of people involved, + and so on. (You will not be required to justify it to the + Copyright Holder, but only to the computing community at large + as a market that must bear the fee.) + + "Freely Available" means that no fee is charged for the item + itself, though there may be fees involved in handling the item. + It also means that recipients of the item may redistribute it + under the same conditions they received it. + +1. You may make and give away verbatim copies of the source form of the +Standard Version of this Package without restriction, provided that you +duplicate all of the original copyright notices and associated disclaimers. + +2. You may apply bug fixes, portability fixes and other modifications +derived from the Public Domain or from the Copyright Holder. A Package +modified in such a way shall still be considered the Standard Version. + +3. You may otherwise modify your copy of this Package in any way, provided +that you insert a prominent notice in each changed file stating how and +when you changed that file, and provided that you do at least ONE of the +following: + + a) place your modifications in the Public Domain or otherwise make them + Freely Available, such as by posting said modifications to Usenet or + an equivalent medium, or placing the modifications on a major archive + site such as uunet.uu.net, or by allowing the Copyright Holder to include + your modifications in the Standard Version of the Package. + + b) use the modified Package only within your corporation or organization. + + c) rename any non-standard executables so the names do not conflict + with standard executables, which must also be provided, and provide + a separate manual page for each non-standard executable that clearly + documents how it differs from the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +4. You may distribute the programs of this Package in object code or +executable form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and library files, + together with instructions (in the manual page or equivalent) on where + to get the Standard Version. + + b) accompany the distribution with the machine-readable source of + the Package with your modifications. + + c) give non-standard executables non-standard names, and clearly + document the differences in manual pages (or equivalent), together + with instructions on where to get the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +5. You may charge a reasonable copying fee for any distribution of this +Package. You may charge any fee you choose for support of this +Package. You may not charge a fee for this Package itself. However, +you may distribute this Package in aggregate with other (possibly +commercial) programs as part of a larger (possibly commercial) software +distribution provided that you do not advertise this Package as a +product of your own. You may embed this Package's interpreter within +an executable of yours (by linking); this shall be construed as a mere +form of aggregation, provided that the complete Standard Version of the +interpreter is so embedded. + +6. The scripts and library files supplied as input to or produced as +output from the programs of this Package do not automatically fall +under the copyright of this Package, but belong to whoever generated +them, and may be sold commercially, and may be aggregated with this +Package. If such scripts or library files are aggregated with this +Package via the so-called "undump" or "unexec" methods of producing a +binary executable image, then distribution of such an image shall +neither be construed as a distribution of this Package nor shall it +fall under the restrictions of Paragraphs 3 and 4, provided that you do +not represent such an executable image as a Standard Version of this +Package. + +7. C subroutines (or comparably compiled subroutines in other +languages) supplied by you and linked into this Package in order to +emulate subroutines and variables of the language defined by this +Package shall not be considered part of this Package, but are the +equivalent of input as in Paragraph 6, provided these subroutines do +not change the language in any way that would cause it to fail the +regression tests for the language. + +8. Aggregation of this Package with a commercial distribution is always +permitted provided that the use of this Package is embedded; that is, +when no overt attempt is made to make this Package's interfaces visible +to the end user of the commercial distribution. Such use shall not be +construed as a distribution of this Package. + +9. The name of the Copyright Holder may not be used to endorse or promote +products derived from this software without specific prior written permission. + +10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED +WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + + The End diff --git a/doc/Copying b/doc/Copying new file mode 100644 index 0000000000..d6400a2c8a --- /dev/null +++ b/doc/Copying @@ -0,0 +1,344 @@ + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software + interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Program does not specify a +version number of this License, you may choose any version ever +published by the Free Software Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + 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 the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/doc/MathObjectsAnswerCheckers.pod b/doc/MathObjectsAnswerCheckers.pod index 35644dcdad..f656d8492f 100644 --- a/doc/MathObjectsAnswerCheckers.pod +++ b/doc/MathObjectsAnswerCheckers.pod @@ -7,7 +7,7 @@ use a single object to produce numeric values, TeX output, and answer strings from a single formula entry. This avoids having to type a function three different ways (which makes maintaining a problem much harder). Since MathObjects also includes vector and complex -arthimetic, it is easier to work with these types of values as well. +arithmetic, it is easier to work with these types of values as well. Secondly using MathObjects improves the processing of student input. This is accomplished through special answer checkers that are part of @@ -670,7 +670,7 @@ student and correct answers to determine if the student list matches the correct list (regardless of order). When the C is called from the list checker, it has two -additional paramters: +additional parameters: checker => sub { my ($correct,$student,$ansHash,$nth,$value) = @_; @@ -725,7 +725,7 @@ Note that in a C for a List object, you will have to do type checking yourself to check if the types of the entries are correct, since a list can consist of any type of elements. -The C should return an array consisting of the nuber of +The C should return an array consisting of the number of student entries that are correct followed by any error messages that should be displayed (each on a separate line in the messages section). @@ -827,7 +827,7 @@ C<$n>$ when the answer is correct. More error checking might be desired, here. For example, since a Union can be of Intervals or Sets, we might want to check that all the student's entries in the Union are actually intervals (the current -version allows Sets). Note thta because the +version allows Sets). Note that because the C parameter is 1 by default, we don't have to check for disjoint intervals. On the other hand, that flag also requires intervals to be merged into one if they could be, e.g., diff --git a/doc/UsingMathObjects.pod b/doc/UsingMathObjects.pod index 79dd16ed1d..3a45b28460 100644 --- a/doc/UsingMathObjects.pod +++ b/doc/UsingMathObjects.pod @@ -95,7 +95,7 @@ name to S>> to get a named function to call: Formula('x^2 + 3')->perlFunction('f'); $y = f(5); -If the formula involves more than one variable, then the paramaters +If the formula involves more than one variable, then the parameters should be given in alphabetical order. Formula('x^2 + y')->perlFunction('f'); @@ -193,7 +193,7 @@ automatically, when necessary. =item * vectors -A vetor is represented C<< >> or C (when used +A vector is represented C<< >> or C (when used in vector context). As with points, vectors can have any number of coordinates. For example, C<< <1,0,0> >>, C<< <-1,3> >>, C<< >>, etc. @@ -341,7 +341,7 @@ so you can use: to produce a TeX version of the vector, just as you can with formulas. There are several "constant" functions that generate common constant -values. These include C, C, C, C and C. you +values. These include C, C, C, C and C. you can use these in perl expressions as though they were their actual values: @@ -368,7 +368,7 @@ interpretation is used, you specify a parser I. The context controls what operations and functions are defined in the parser, what variables and constants to allow, how to interpret -various paretheses, and so on. Changing the context can completely +various parentheses, and so on. Changing the context can completely change the way a formula is interpreted. There are several predefined contexts: S>>, Sconstants->get('M'); -will return the value of the consant C. (See the +will return the value of the constant C. (See the F file for more information on the methods you can call for the various types of context data.) diff --git a/docker/pg.Dockerfile b/docker/pg.Dockerfile index 0ce87be560..52b755516c 100644 --- a/docker/pg.Dockerfile +++ b/docker/pg.Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:24.04 +FROM ubuntu:26.04 ENV DEBIAN_FRONTEND=noninteractive ENV DEBCONF_NONINTERACTIVE_SEEN=true @@ -20,8 +20,8 @@ RUN apt-get update \ liblocale-maketext-lexicon-perl \ libmojolicious-perl \ libtest-mockobject-perl \ - libtest2-suite-perl \ libtie-ixhash-perl \ + libtypes-serialiser-perl \ libuuid-tiny-perl \ libyaml-libyaml-perl \ make \ diff --git a/htdocs/generate-assets.js b/htdocs/generate-assets.js index 91647b2c6f..445eff3677 100755 --- a/htdocs/generate-assets.js +++ b/htdocs/generate-assets.js @@ -2,7 +2,8 @@ /* eslint-env node */ -const yargs = require('yargs'); +const yargs = require('yargs/yargs'); +const { hideBin } = require('yargs/helpers'); const chokidar = require('chokidar'); const path = require('path'); const { minify } = require('terser'); @@ -14,7 +15,7 @@ const postcss = require('postcss'); const rtlcss = require('rtlcss'); const cssMinify = require('cssnano'); -const argv = yargs +const argv = yargs(hideBin(process.argv)) .usage('$0 Options') .version(false) .alias('help', 'h') diff --git a/htdocs/helpFiles/Entering-Angles.html b/htdocs/helpFiles/Entering-Angles.html index bfa15541e0..b0837609f1 100644 --- a/htdocs/helpFiles/Entering-Angles.html +++ b/htdocs/helpFiles/Entering-Angles.html @@ -43,7 +43,7 @@

Entering Angles

diff --git a/htdocs/helpFiles/Entering-Equations.html b/htdocs/helpFiles/Entering-Equations.html index bd758fdda8..b05fd50386 100644 --- a/htdocs/helpFiles/Entering-Equations.html +++ b/htdocs/helpFiles/Entering-Equations.html @@ -45,7 +45,7 @@

Entering Equations

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Entering-Formulas.html b/htdocs/helpFiles/Entering-Formulas.html index 60e8dea8b6..21b40231db 100644 --- a/htdocs/helpFiles/Entering-Formulas.html +++ b/htdocs/helpFiles/Entering-Formulas.html @@ -107,7 +107,7 @@

    Entering Formulas

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Entering-Formulas10.html b/htdocs/helpFiles/Entering-Formulas10.html index 7f60e91f56..8b00add2f9 100644 --- a/htdocs/helpFiles/Entering-Formulas10.html +++ b/htdocs/helpFiles/Entering-Formulas10.html @@ -107,7 +107,7 @@

    Entering Formulas

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Entering-Logarithms.html b/htdocs/helpFiles/Entering-Logarithms.html index ed055d68fc..b38374e0b0 100644 --- a/htdocs/helpFiles/Entering-Logarithms.html +++ b/htdocs/helpFiles/Entering-Logarithms.html @@ -54,7 +54,7 @@

    Entering Logarithms

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Entering-Logarithms10.html b/htdocs/helpFiles/Entering-Logarithms10.html index 39b764f85e..de0bba372f 100644 --- a/htdocs/helpFiles/Entering-Logarithms10.html +++ b/htdocs/helpFiles/Entering-Logarithms10.html @@ -55,7 +55,7 @@

    Entering Logarithms

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Entering-Points.html b/htdocs/helpFiles/Entering-Points.html index 077cf66fa0..a5b23d448c 100644 --- a/htdocs/helpFiles/Entering-Points.html +++ b/htdocs/helpFiles/Entering-Points.html @@ -37,7 +37,7 @@

    Entering Points

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Entering-Syntax.html b/htdocs/helpFiles/Entering-Syntax.html index c8793fd408..ccca4ce042 100644 --- a/htdocs/helpFiles/Entering-Syntax.html +++ b/htdocs/helpFiles/Entering-Syntax.html @@ -246,4 +246,4 @@

    Other Mathematical Func For more information see the -list of all available functions. +list of all available functions. diff --git a/htdocs/helpFiles/Entering-Units.html b/htdocs/helpFiles/Entering-Units.html index 9ac3fc9616..e8a4912770 100644 --- a/htdocs/helpFiles/Entering-Units.html +++ b/htdocs/helpFiles/Entering-Units.html @@ -219,5 +219,5 @@

    Units Available in WeBWorK

    - More details on units in WeBWorK + More details on units in WeBWorK

    diff --git a/htdocs/helpFiles/Entering-Vectors.html b/htdocs/helpFiles/Entering-Vectors.html index 7a1523b13b..7d7d2339cc 100644 --- a/htdocs/helpFiles/Entering-Vectors.html +++ b/htdocs/helpFiles/Entering-Vectors.html @@ -64,7 +64,7 @@

    Entering Vectors

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Syntax.html b/htdocs/helpFiles/Syntax.html index c8793fd408..ccca4ce042 100644 --- a/htdocs/helpFiles/Syntax.html +++ b/htdocs/helpFiles/Syntax.html @@ -246,4 +246,4 @@

    Other Mathematical Func For more information see the -list of all available functions. +list of all available functions. diff --git a/htdocs/helpFiles/Units.html b/htdocs/helpFiles/Units.html index 456bc1ada4..02cea15387 100644 --- a/htdocs/helpFiles/Units.html +++ b/htdocs/helpFiles/Units.html @@ -199,5 +199,5 @@

    Units Available in WeBWorK

    - More details on units in WeBWorK + More details on units in WeBWorK

    diff --git a/htdocs/js/AppletSupport/ww_applet_support.js b/htdocs/js/AppletSupport/ww_applet_support.js index 4dfd18a01e..e9d599d8c3 100644 --- a/htdocs/js/AppletSupport/ww_applet_support.js +++ b/htdocs/js/AppletSupport/ww_applet_support.js @@ -95,7 +95,6 @@ class ww_applet { if (typeof newState === 'undefined') newState = 'restart_applet'; const stateInput = ww_applet_list[this.appletName].stateInput; getQE(stateInput).value = newState; - getQE(`previous_${stateInput}`).value = newState; } // STATE: @@ -108,7 +107,7 @@ class ww_applet { // Communication with the applet is in plain text, not in base64 code. if (state) { - if (this.debug) console.log(`${this.appletName}: Obtain state from calling paramater`); + if (this.debug) console.log(`${this.appletName}: Obtain state from calling parameter`); } else { if (this.debug) console.log(`${this.appletName}: Obtain state from ${this.stateInput}`); // Hidden answer box preserving applet state @@ -275,12 +274,14 @@ class ww_applet { if (form.submitHandlerInitialized) return; form.submitHandlerInitialized = true; - // Connect the submit action handler to the form. - form.addEventListener('submit', () => { - for (const appletName in ww_applet_list) { - ww_applet_list[appletName].submitAction(); - } - }); + // Connect the submit action handler to the form submit buttons. + for (const button of form.querySelectorAll('input[type="submit"]')) { + button.addEventListener('click', () => { + for (const appletName in ww_applet_list) { + ww_applet_list[appletName].submitAction(); + } + }); + } }; // Initialize applet support and the applets. diff --git a/htdocs/js/DragNDrop/dragndrop.js b/htdocs/js/DragNDrop/dragndrop.js index 4c45cd72c0..f4f2edc9a4 100644 --- a/htdocs/js/DragNDrop/dragndrop.js +++ b/htdocs/js/DragNDrop/dragndrop.js @@ -8,29 +8,38 @@ this.answerName = el.dataset.answerName ?? ''; this.buckets = []; - this.removeButtonText = el.dataset.removeButtonText ?? 'Remove'; - this.answerInput = el.parentElement.querySelector(`input[name="${this.answerName}"]`); + this.answerInput = el.parentElement?.querySelector(`input[name="${this.answerName}"]`); if (!this.answerInput) { // This should not happen if using the macros. alert(`FATAL ERROR: Unable to find answer input corresponding to ${this.answerName}.`); return; } - this.bucketContainer = document.createElement('div'); - this.bucketContainer.classList.add('dd-pool-bucket-container'); - el.prepend(this.bucketContainer); + el.role = 'application'; this.itemList = JSON.parse(el.dataset.itemList ?? '[]'); this.defaultState = JSON.parse(el.dataset.defaultState ?? '[]'); + this.showUniversalSet = 'showUniversalSet' in el.dataset; + + // Translatable/customizable text. this.labelFormat = el.dataset.labelFormat; + this.removeButtonText = el.dataset.removeButtonText ?? 'Remove'; + this.universalSetLabel = el.dataset.universalSetLabel ?? 'Universal Set'; + this.addFromUniversalText = + el.dataset.addFromUniversalText ?? 'Item %1s in the universal set added as item %2s to list %3s.'; + this.removeUniversalItemText = el.dataset.removeUniversalItemText ?? 'Item %1s removed from list %2s.'; + this.reorderText = el.dataset.reorderText ?? 'Moved item %1s in list %2s to item %3s.'; + this.moveText = el.dataset.moveText ?? 'Moved item %1s in list %2s to item %3s in list %4s.'; - this.showUniversalSet = 'showUniversalSet' in el.dataset; + this.bucketContainer = document.createElement('div'); + this.bucketContainer.classList.add('dd-pool-bucket-container'); + el.prepend(this.bucketContainer); if (this.answerInput.value) { // Need to check for things like (3,2,1) for backwards compatibility. Now it will be {3,2,1}. - const matches = this.answerInput.value.match(/((?:\{|\()[^\{\}\(\)]*(?:\}|\)))/g); - for (const match of matches) { + const matches = this.answerInput.value.match(/((?:\{|\()[^{}()]*(?:\}|\)))/g); + for (const match of matches ?? []) { const i = this.buckets.length; const bucket = { removable: i < this.defaultState.length ? this.defaultState[i].removable : 1, @@ -39,6 +48,7 @@ .replaceAll(/\{|\}|\(|\)/g, '') .split(',') .filter((index) => index !== '') + .map((i) => parseInt(i)) }; this.buckets.push(new Bucket(this, i, bucket)); } @@ -57,7 +67,7 @@ this.universalSetBucket = new Bucket(this, this.buckets.length, { isUniversalSet: true, removable: false, - label: el.dataset.universalSetLabel ?? 'Universal Set', + label: this.universalSetLabel, indices: this.itemList.map((_el, i) => i) }); } @@ -81,11 +91,136 @@ } this.buckets = []; - for (const bucket of this.defaultState) { + for (const bucket of this.defaultState ?? []) { this.buckets.push(new Bucket(this, this.buckets.length, bucket)); } this.updateAnswerInput(); }); + + this.announcer = document.createElement('div'); + this.announcer.setAttribute('aria-live', 'assertive'); + this.announcer.className = 'visually-hidden'; + el.append(this.announcer); + + el.addEventListener('keydown', (e) => { + if ( + !['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key) || + !e.target || + !(e.target instanceof HTMLElement) + ) + return; + const item = e.target.closest('.dd-item'); + if (!item || !(item instanceof HTMLElement)) return; + + if (this.universalSetBucket) { + const children = this.universalSetBucket.items; + const index = children.indexOf(item); + if (index !== -1) { + e.preventDefault(); + let toBucketIndex = 0; + if (e.key === 'ArrowRight' || e.key === 'ArrowDown') { + while ( + toBucketIndex < this.buckets.length && + this.buckets[toBucketIndex].hasItem(item.dataset.id) + ) + ++toBucketIndex; + } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') { + toBucketIndex = this.buckets.length - 1; + while (toBucketIndex >= 0 && this.buckets[toBucketIndex].hasItem(item.dataset.id)) + --toBucketIndex; + } + if (toBucketIndex >= 0 && toBucketIndex < this.buckets.length) { + this.copyUniversalItem(item, this.buckets[toBucketIndex]); + this.announce( + this.addFromUniversalText, + index + 1, + this.buckets[toBucketIndex].items.length, + toBucketIndex + 1 + ); + } + this.updateAnswerInput(); + return; + } + } + + for (const [bucketIndex, bucket] of this.buckets.entries()) { + const children = bucket.items; + const index = children.indexOf(item); + if (index === -1) continue; + + e.preventDefault(); + + if (e.key === 'ArrowUp' && index > 0) { + bucket.swapItems(item, children[index - 1]); + this.announce(this.reorderText, index + 1, bucketIndex + 1, index); + } else if (e.key === 'ArrowDown' && index < children.length - 1) { + bucket.swapItems(item, children[index + 1]); + this.announce(this.reorderText, index + 1, bucketIndex + 1, index + 2); + } else if ( + e.key === 'ArrowLeft' && + (bucketIndex > 0 || (this.universalSetBucket && bucketIndex === 0)) + ) { + let toBucketIndex = bucketIndex - 1; + if (this.universalSetBucket) { + while (toBucketIndex >= 0 && this.buckets[toBucketIndex].hasItem(item.dataset.id)) + --toBucketIndex; + } + if (toBucketIndex >= 0) { + this.moveItem(item, bucket, this.buckets[toBucketIndex]); + this.announce( + this.moveText, + index + 1, + bucketIndex + 1, + this.buckets[toBucketIndex].items.length, + toBucketIndex + 1 + ); + } else if (this.universalSetBucket) { + bucket.removeItem(item); + for (const universalItem of this.universalSetBucket.items) { + if (universalItem.dataset.id === item.dataset.id) { + universalItem.focus(); + break; + } + } + this.announce(this.removeUniversalItemText, index + 1, bucketIndex + 1); + } + } else if ( + e.key === 'ArrowRight' && + (bucketIndex < this.buckets.length - 1 || + (this.universalSetBucket && bucketIndex === this.buckets.length - 1)) + ) { + let toBucketIndex = bucketIndex + 1; + if (this.universalSetBucket) { + while ( + toBucketIndex < this.buckets.length && + this.buckets[toBucketIndex].hasItem(item.dataset.id) + ) + ++toBucketIndex; + } + if (toBucketIndex < this.buckets.length) { + this.moveItem(item, bucket, this.buckets[toBucketIndex]); + this.announce( + this.moveText, + index + 1, + bucketIndex + 1, + this.buckets[toBucketIndex].items.length, + toBucketIndex + 1 + ); + } else if (this.universalSetBucket) { + bucket.removeItem(item); + for (const universalItem of this.universalSetBucket.items) { + if (universalItem.dataset.id === item.dataset.id) { + universalItem.focus(); + break; + } + } + this.announce(this.removeUniversalItemText, index + 1, bucketIndex + 1); + } + } + this.updateAnswerInput(); + break; + } + }); } updateAnswerInput() { @@ -98,6 +233,142 @@ this.answerInput.value = '(' + contents.join(',') + ')'; } + + announce(message, ...replacements) { + let interpolatedMessage = message; + for (const interpolation of message.matchAll(/(%(\d)s)/g)) { + const position = parseInt(interpolation[2]) - 1; + interpolatedMessage = interpolatedMessage.replace(interpolation[1], replacements[position]); + } + this.announcer.textContent = interpolatedMessage; + } + + // Move a list item from one bucket to another and animate the changes to the DOM. + moveItem(element, fromBucket, toBucket) { + const siblings = fromBucket.items.filter((e) => e !== element); + + const fromBucketHeight = fromBucket.el.getBoundingClientRect().height; + const toBucketHeight = toBucket.el.getBoundingClientRect().height; + const elementRect = element.getBoundingClientRect(); + const siblingRects = siblings.map((e) => e.getBoundingClientRect()); + + toBucket.ddList.append(element); + element.focus(); + + const newFromBucketHeight = fromBucket.el.getBoundingClientRect().height; + const newToBucketHeight = toBucket.el.getBoundingClientRect().height; + const newElementRect = element.getBoundingClientRect(); + const newSiblingRects = siblings.map((e) => e.getBoundingClientRect()); + + requestAnimationFrame(() => { + for (const [bucket, height, newHeight] of [ + [fromBucket, fromBucketHeight, newFromBucketHeight], + [toBucket, toBucketHeight, newToBucketHeight] + ]) { + bucket.el.animate([{ height: `${height}px` }, { height: `${newHeight}px` }], { + duration: 150, + easing: 'ease' + }); + } + + element.style.position = 'fixed'; + element.style.top = `${newElementRect.top}px`; + element.style.left = `${newElementRect.left}px`; + element.style.width = `${newElementRect.width}px`; + element.style.pointerEvents = 'none'; + + element + .animate( + [ + { + transformOrigin: 'top left', + transform: `translate(${ + elementRect.left - newElementRect.left + }px, ${elementRect.top - newElementRect.top}px)` + }, + { transformOrigin: 'top left', transform: 'none' } + ], + { duration: 150, easing: 'ease' } + ) + .finished.then(() => { + element.style.position = ''; + element.style.top = ''; + element.style.left = ''; + element.style.width = ''; + element.style.pointerEvents = ''; + element.focus(); + }) + .catch(() => { + /* ignore */ + }); + + for (const [index, sibling] of siblings.entries()) { + sibling.animate( + [ + { + transformOrigin: 'top left', + transform: `translate(${ + siblingRects[index].left - newSiblingRects[index].left + }px, ${siblingRects[index].top - newSiblingRects[index].top}px)` + }, + { transformOrigin: 'top left', transform: 'none' } + ], + { duration: 150, easing: 'ease' } + ); + } + }); + } + + // Copy an item from the universal set bucket to another bucket and animate it moving there. + copyUniversalItem(element, toBucket) { + const toBucketHeight = toBucket.el.getBoundingClientRect().height; + const elementRect = element.getBoundingClientRect(); + + const elementCopy = element.cloneNode(true); + toBucket.ddList.append(elementCopy); + elementCopy.focus(); + + const newToBucketHeight = toBucket.el.getBoundingClientRect().height; + const newElementRect = elementCopy.getBoundingClientRect(); + + requestAnimationFrame(() => { + toBucket.el.animate([{ height: `${toBucketHeight}px` }, { height: `${newToBucketHeight}px` }], { + duration: 150, + easing: 'ease' + }); + + elementCopy.style.position = 'fixed'; + elementCopy.style.top = `${newElementRect.top}px`; + elementCopy.style.left = `${newElementRect.left}px`; + elementCopy.style.width = `${newElementRect.width}px`; + elementCopy.style.pointerEvents = 'none'; + + elementCopy + .animate( + [ + { + transformOrigin: 'top left', + transform: `translate(${ + elementRect.left - newElementRect.left + }px, ${elementRect.top - newElementRect.top}px)` + }, + { transformOrigin: 'top left', transform: 'none' } + ], + { duration: 150, easing: 'ease' } + ) + .finished.then(() => { + elementCopy.style.position = ''; + elementCopy.style.top = ''; + elementCopy.style.left = ''; + elementCopy.style.width = ''; + elementCopy.style.pointerEvents = ''; + elementCopy.focus(); + }) + .catch(() => { + /* ignore */ + }); + }); + } } class Bucket { @@ -105,20 +376,21 @@ this.id = id; this.bucketPool = bucketPool; - this.el = this.htmlBucket(bucketData.label, bucketData.removable, bucketData.indices); + this.el = this.htmlBucket(bucketData.label ?? '', bucketData.removable ?? 0, bucketData.indices); - if (bucketData.isUniversalSet) bucketPool.universalSetContainer.append(this.el); + if (bucketData.isUniversalSet) bucketPool.universalSetContainer?.append(this.el); else bucketPool.bucketContainer.append(this.el); // Typeset any math content that may be in the added html. - if (window.MathJax) { - MathJax.startup.promise = MathJax.startup.promise.then(() => MathJax.typesetPromise([this.el])); - } + if (window.MathJax) window.MathJax.typesetPromise?.([this.el]); const options = { group: { name: bucketPool.answerName }, animation: 150, - onEnd: () => this.bucketPool.updateAnswerInput() + onEnd: (evt) => { + evt.item.focus(); + this.bucketPool.updateAnswerInput(); + } }; if (bucketPool.showUniversalSet) { @@ -128,8 +400,7 @@ options.group.put = false; } else { options.removeOnSpill = true; - options.group.put = (to, _from, dragEl) => - !Array.from(to.el.children).some((child) => child.dataset.id === dragEl.dataset.id); + options.group.put = (to, _from, dragEl) => !to.toArray().some((id) => id === dragEl.dataset.id); } } @@ -143,8 +414,7 @@ const bucketLabel = document.createElement('div'); bucketLabel.classList.add('dd-bucket-label'); bucketLabel.innerHTML = - label || - (this.bucketPool.labelFormat ? `${this.bucketPool.labelFormat.replace(/%s/, this.id + 1)}` : ''); + label || (this.bucketPool.labelFormat ? this.bucketPool.labelFormat.replace(/%s/, this.id + 1) : ''); this.ddList = document.createElement('div'); this.ddList.classList.add('dd-list'); @@ -152,17 +422,20 @@ bucketElement.append(bucketLabel, this.ddList); for (const index of indices) { - if (index < 0 || index > this.bucketPool.itemList.length) continue; + if (index < 0 || index > (this.bucketPool.itemList?.length ?? 0)) continue; const listElement = document.createElement('div'); listElement.classList.add('dd-item'); + listElement.role = 'button'; + listElement.draggable = true; + listElement.tabIndex = 0; listElement.dataset.id = index; - listElement.innerHTML = this.bucketPool.itemList[index]; + listElement.innerHTML = this.bucketPool.itemList?.[index] ?? ''; this.ddList.append(listElement); } - bucketElement.style.backgroundColor = `hsla(${(100 + this.id * 100) % 360}, 40%, 90%, 1)`; + bucketElement.style.backgroundColor = `hsl(${(100 + this.id * 100) % 360} 40% 90%)`; // The first bucket is not allowed to be removable. if (this.id !== 0 && removable) { @@ -172,12 +445,10 @@ removeButton.textContent = this.bucketPool.removeButtonText; removeButton.addEventListener('click', () => { - const firstBucketList = this.bucketPool.buckets[0].ddList; - const firstBucketListItems = Array.from(firstBucketList.querySelectorAll('.dd-item')).map( - (item) => item.dataset.id - ); - for (const item of this.ddList.querySelectorAll('.dd-item')) { - if (!firstBucketListItems.includes(item.dataset.id)) firstBucketList.append(item); + const firstBucketListItemIds = this.bucketPool.buckets[0].sortable.toArray(); + for (const item of this.items) { + if (typeof item.dataset.id !== 'undefined' && !firstBucketListItemIds.includes(item.dataset.id)) + this.bucketPool.buckets[0].ddList.append(item); } bucketElement.remove(); @@ -191,6 +462,35 @@ return bucketElement; } + + get items() { + return Array.from(this.ddList.children); + } + + hasItem(id) { + return this.items.some((i) => i.dataset.id === id); + } + + // Swap the position of two list items and animate the change. + swapItems(element1, element2) { + const listIds = this.sortable.toArray(); + const element1Index = listIds.findIndex((i) => i === element1.dataset.id); + const element2Index = this.sortable.toArray().findIndex((i) => i === element2.dataset.id); + if (element1Index === -1 || element2Index === -1) return; + [listIds[element1Index], listIds[element2Index]] = [listIds[element2Index], listIds[element1Index]]; + this.sortable.sort(listIds, true); + element1.focus(); + } + + // Remove a list item and animate the remaining list items moving up to fill its place. + removeItem(element) { + const listIds = this.sortable.toArray(); + const elementIndex = listIds.findIndex((i) => i === element.dataset.id); + if (elementIndex === -1) return; + listIds.splice(elementIndex, 1); + this.sortable.sort(listIds, true); + element.remove(); + } } // Set up bucket pools that are already in the page. @@ -202,7 +502,7 @@ const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { for (const node of mutation.addedNodes) { - if (node instanceof Element) { + if (node instanceof HTMLElement) { if (node.classList.contains('dd-bucket-pool')) { if (!node.dataset.bucketPoolInitialized) new BucketPool(node); } else { diff --git a/htdocs/js/DragNDrop/dragndrop.scss b/htdocs/js/DragNDrop/dragndrop.scss index 54229e2ead..8dee4a354d 100644 --- a/htdocs/js/DragNDrop/dragndrop.scss +++ b/htdocs/js/DragNDrop/dragndrop.scss @@ -1,8 +1,7 @@ .dd-bucket-pool { .dd-pool-bucket-container { display: flex; - flex-direction: row; - flex-wrap: wrap; + flex-flow: row wrap; justify-content: space-evenly; gap: 1rem; margin-bottom: 1rem; @@ -12,16 +11,19 @@ flex-direction: column; width: 350px; padding: 0.5rem; - color: #000000; + color: #000; border: 1px solid #388e8e; border-radius: 5px; text-align: center; .dd-bucket-label { - margin: 0 0 10px 0; + margin: 0 0 10px; } .dd-list { + list-style: none; + margin: 0; + padding: 0; display: flex; flex-direction: column; flex-grow: 1; @@ -48,11 +50,28 @@ text-align: center; height: auto; - &:hover { + &:hover:not(.sortable-chosen) { cursor: pointer; background: #eee3ce; color: #222; } + + &:focus { + border-color: blue; + box-shadow: inset 0 1px 0 #ffffff26; + filter: drop-shadow(0px 5px 10px rgb(0 0 255 / 75%)); + outline: 0; + } + + &.sortable-ghost { + background: #f5f5f5; + border-color: blue; + box-shadow: inset 0 1px 0 #ffffff26; + filter: drop-shadow(0px 5px 10px rgb(0 0 255 / 75%)); + + // Override an opacity setting set by webwork2 for the problem set detail page. + opacity: 1 !important; + } } } } @@ -69,4 +88,26 @@ margin-top: 0.5rem; align-self: center; } + + .dd-keyboard-help { + summary { + list-style: none; + } + + .dd-keyboard-help-content { + position: fixed; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: max-content; + max-width: 750px; + box-shadow: 0 0 100px black; + border-radius: 0.375rem; + z-index: 10; + + @media only screen and (width <= 768px) { + max-width: 90%; + } + } + } } diff --git a/htdocs/js/DropDown/dropdown.js b/htdocs/js/DropDown/dropdown.js index 170a96c837..b85b48ccac 100644 --- a/htdocs/js/DropDown/dropdown.js +++ b/htdocs/js/DropDown/dropdown.js @@ -28,8 +28,7 @@ dropdownBtn.textContent = option.dataset.content; dropdownBtn.focus(); - if (window.MathJax) - MathJax.startup.promise = MathJax.startup.promise.then(() => MathJax.typesetPromise([dropdownBtn])); + if (window.MathJax) MathJax.typesetPromise?.([dropdownBtn]); // If any feedback popovers are open, then update their positions. for (const popover of document.querySelectorAll('.ww-feedback-btn')) { diff --git a/htdocs/js/Essay/essay.js b/htdocs/js/Essay/essay.js index 3c7a747e7c..1ad3d93b54 100644 --- a/htdocs/js/Essay/essay.js +++ b/htdocs/js/Essay/essay.js @@ -49,11 +49,9 @@ ); if (window.MathJax) { button.addEventListener( - 'show.bs.popover', + 'inserted.bs.popover', () => { - MathJax.startup.promise = MathJax.startup.promise.then(() => - MathJax.typesetPromise(['.popover-body']) - ); + MathJax.typesetPromise?.([popover.tip]); }, { once: true } ); diff --git a/htdocs/js/Feedback/feedback.js b/htdocs/js/Feedback/feedback.js index 5328334ec2..b4de5d39f7 100644 --- a/htdocs/js/Feedback/feedback.js +++ b/htdocs/js/Feedback/feedback.js @@ -11,16 +11,10 @@ }); feedbackPopovers.push(feedbackPopover); - // Render MathJax previews. - if (window.MathJax) { - feedbackBtn.addEventListener('show.bs.popover', () => { - MathJax.startup.promise = MathJax.startup.promise.then(() => - MathJax.typesetPromise(['.popover-body']).then(() => feedbackPopover.update()) - ); - }); - } + feedbackBtn.addEventListener('inserted.bs.popover', () => { + // Render MathJax previews. + if (window.MathJax) MathJax.typesetPromise?.([feedbackPopover.tip]).then(() => feedbackPopover.update()); - feedbackBtn.addEventListener('shown.bs.popover', () => { // Execute javascript in the answer preview. feedbackPopover.tip?.querySelectorAll('script').forEach((origScript) => { const newScript = document.createElement('script'); @@ -47,12 +41,17 @@ if (feedbackPopover.tip) feedbackPopover.tip.dataset.iframeHeight = '1'; const revealCorrectBtn = feedbackPopover.tip?.querySelector('.reveal-correct-btn'); - if (revealCorrectBtn && feedbackPopover.correctRevealed) { - revealCorrectBtn.nextElementSibling?.classList.remove('d-none'); - revealCorrectBtn.remove(); - } else { - revealCorrectBtn?.addEventListener('click', () => { - feedbackPopover.correctRevealed = true; + if (revealCorrectBtn) { + const fragment = new DocumentFragment(); + const container = document.createElement('div'); + container.innerHTML = feedbackBtn.dataset.bsContent; + const button = container.querySelector('.reveal-correct-btn'); + button?.nextElementSibling?.classList.remove('d-none'); + button?.remove(); + fragment.append(container); + const feedbackNoRevealBtn = fragment.firstElementChild.innerHTML; + + revealCorrectBtn.addEventListener('click', () => { revealCorrectBtn.classList.add('fade-out'); revealCorrectBtn.parentElement.classList.add('resize-transition'); revealCorrectBtn.parentElement.style.maxWidth = `${revealCorrectBtn.parentElement.offsetWidth}px`; @@ -65,6 +64,12 @@ revealCorrectBtn.remove(); feedbackPopover.update(); }); + + feedbackBtn.addEventListener( + 'hidden.bs.popover', + () => feedbackPopover.setContent({ '.popover-body': feedbackNoRevealBtn }), + { once: true } + ); }); } }); diff --git a/htdocs/js/GraphTool/circletool.js b/htdocs/js/GraphTool/circletool.js index 50b9c663fc..0021ed02ba 100644 --- a/htdocs/js/GraphTool/circletool.js +++ b/htdocs/js/GraphTool/circletool.js @@ -16,9 +16,19 @@ fixed: true, highlight: false, strokeColor: gt.color.curve, - dash: solid ? 0 : 2 + dash: solid ? 0 : 2, + tabindex: '' }) ); + this.baseObj.setAttribute({ + aria: { + enabled: true, + label: this.constructor.ariaLabel, + roledescription: this.constructor.strId, + live: 'assertive', + atomic: true + } + }); this.definingPts.push(center, point); this.focusPoint = center; @@ -47,6 +57,14 @@ ); } + static ariaLabel(c) { + return ( + (c.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` circle centered at ${c.center.X()}, ${c.center.Y()} and ` + + `passing through the point ${c.point2.X()}, ${c.point2.Y()}` + ); + } + static restore(string) { let pointData = gt.pointRegexp.exec(string); const points = []; @@ -116,7 +134,9 @@ highlight: false, snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, - withLabel: false + withLabel: false, + tabindex: 0, + aria: gt.pointAria }); this.hlObjs.hl_point.rendNode.focus(); } @@ -129,7 +149,15 @@ fixed: true, strokeColor: gt.color.underConstruction, highlight: false, - dash: gt.drawSolid ? 0 : 2 + dash: gt.drawSolid ? 0 : 2, + tabindex: '', + aria: { + enabled: true, + label: gt.graphObjectTypes[this.object].ariaLabel, + roledescription: this.object, + live: 'assertive', + atomic: true + } }); } @@ -151,7 +179,9 @@ highlight: false, snapToGrid: true, snapSizeX: gt.snapSizeX, - snapSizeY: gt.snapSizeY + snapSizeY: gt.snapSizeY, + tabindex: '', + aria: gt.pointAria }); this.center.setAttribute({ fixed: true }); @@ -188,7 +218,7 @@ const center = this.center; delete this.center; - center.setAttribute(gt.definingPointAttributes); + center.setAttribute(gt.definingPointAttributes()); center.on('down', () => gt.onPointDown(center)); center.on('up', () => gt.onPointUp(center)); diff --git a/htdocs/js/GraphTool/cubictool.js b/htdocs/js/GraphTool/cubictool.js index c65983a3f4..0bd133925b 100644 --- a/htdocs/js/GraphTool/cubictool.js +++ b/htdocs/js/GraphTool/cubictool.js @@ -12,7 +12,7 @@ constructor(point1, point2, point3, point4, solid) { for (const point of [point1, point2, point3, point4]) { - point.setAttribute(gt.definingPointAttributes); + point.setAttribute(gt.definingPointAttributes()); if (!gt.isStatic) { point.on('down', () => gt.onPointDown(point)); point.on('up', () => gt.onPointUp(point)); @@ -98,7 +98,19 @@ strokeWidth: 2, highlight: false, strokeColor: color ? color : gt.color.underConstruction, - dash: solid ? 0 : 2 + dash: solid ? 0 : 2, + tabindex: '', + aria: { + enabled: true, + label: (c) => + (c.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` cubic passing through ${point1.X()}, ${point1.Y()}, and ` + + `${point2.X()}, ${point2.Y()}, and ${point3.X()}, ${point3.Y()}, ` + + `and ${point4.X()}, ${point4.Y()}`, + roledescription: 'cubic', + live: 'assertive', + atomic: true + } } ); } @@ -294,7 +306,9 @@ snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, highlight: false, - withLabel: false + withLabel: false, + tabindex: '', + aria: gt.pointAria }); this.hlObjs.hl_point.rendNode.focus(); } @@ -341,7 +355,18 @@ fixed: true, strokeColor: gt.color.underConstruction, highlight: false, - dash: gt.drawSolid ? 0 : 2 + dash: gt.drawSolid ? 0 : 2, + tabindex: '', + aria: { + enabled: true, + label: (l) => + (l.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` line through ${l.point1.X()}, ${l.point1.Y()}, ` + + `and ${l.point2.X()}, ${l.point2.Y()}`, + roledescription: 'line', + live: 'assertive', + atomic: true + } }); } diff --git a/htdocs/js/GraphTool/filltool.js b/htdocs/js/GraphTool/filltool.js index 3935edfbcd..2c2331bc86 100644 --- a/htdocs/js/GraphTool/filltool.js +++ b/htdocs/js/GraphTool/filltool.js @@ -21,7 +21,15 @@ highlightStrokeOpacity: 0, fillOpacity: 0, highlightFillOpacity: 0, - fixed: gt.isStatic + fixed: gt.isStatic, + tabindex: 0, + aria: { + enabled: true, + label: (p) => `shade the region containing the point ${p.X()}, ${p.Y()}`, + roledescription: 'shading point', + live: 'assertive', + atomic: true + } }); this.definingPts.push(point); this.focusPoint = point; @@ -41,7 +49,15 @@ [() => point.X() - 12 / gt.board.unitX, () => point.Y() - 12 / gt.board.unitY], [() => 24 / gt.board.unitX, () => 24 / gt.board.unitY] ], - { withLabel: false, highlight: false, layer: 8, name: 'FillIcon', fixed: true } + { + withLabel: false, + highlight: false, + layer: 8, + name: 'FillIcon', + fixed: true, + tabindex: '', + aria: { enabled: true, hidden: true, live: 'off' } + } ); if (!gt.isStatic) { @@ -226,7 +242,21 @@ this.fillObj = gt.board.create( 'image', [dataURL, [bBox[0], bBox[3]], [bBox[2] - bBox[0], bBox[1] - bBox[3]]], - { withLabel: false, highlight: false, fixed: true, layer: 0 } + { + withLabel: false, + highlight: false, + fixed: true, + layer: 0, + tabindex: '', + aria: { + enabled: true, + label: () => + `shaded region containing the point ${this.baseObj.X()}, ${this.baseObj.Y()}`, + roledescription: 'shading', + live: 'assertive', + atomic: true + } + } ); }; @@ -343,7 +373,15 @@ withLabel: false, snapToGrid: true, snapSizeX: gt.snapSizeX, - snapSizeY: gt.snapSizeY + snapSizeY: gt.snapSizeY, + tabindex: 0, + aria: { + enabled: true, + label: (p) => `shade the region containing the point ${p.X()}, ${p.Y()}`, + roledescription: 'shading point', + live: 'assertive', + atomic: true + } }); this.hlObjs.hl_point.rendNode.classList.add('hidden-fill-point'); @@ -357,7 +395,14 @@ ], [() => 24 / gt.board.unitX, () => 24 / gt.board.unitY] ], - { withLabel: false, highlight: false, fixed: true, layer: 8 } + { + withLabel: false, + highlight: false, + fixed: true, + layer: 8, + tabindex: '', + aria: { enabled: true, hidden: true, live: 'off' } + } ); this.hlObjs.hl_point.rendNode.focus(); diff --git a/htdocs/js/GraphTool/graphtool.js b/htdocs/js/GraphTool/graphtool.js index dcfcfebf81..db0ef2f5e2 100644 --- a/htdocs/js/GraphTool/graphtool.js +++ b/htdocs/js/GraphTool/graphtool.js @@ -1,4 +1,4 @@ -/* global JXG */ +/* global JXG, MathJax */ 'use strict'; @@ -14,6 +14,7 @@ window.graphTool = (containerId, options) => { setTimeout(() => window.graphTool(containerId, options), 100); return; } + gt.graphContainer.role = 'application'; // Semantic color control gt.color = { @@ -39,7 +40,7 @@ window.graphTool = (containerId, options) => { underConstructionFixed: JXG.palette.red // defined to be '#d55e00' }; - gt.definingPointAttributes = { + gt.definingPointAttributes = () => ({ size: 3, fixed: false, highlight: true, @@ -49,8 +50,9 @@ window.graphTool = (containerId, options) => { fillColor: gt.color.point, highlightStrokeWidth: 1, highlightStrokeColor: gt.color.focusCurve, - highlightFillColor: gt.color.pointHighlight - }; + highlightFillColor: gt.color.pointHighlight, + tabindex: gt.isStatic ? -1 : 0 + }); gt.options = options; gt.snapSizeX = options.snapSizeX ? options.snapSizeX : 1; @@ -69,7 +71,6 @@ window.graphTool = (containerId, options) => { if ('htmlInputId' in options) gt.html_input = document.getElementById(options.htmlInputId); const cfgOptions = { title: 'WeBWorK Graph Tool', - description: options.ariaDescription ?? 'Interactively graph objects', showCopyright: false, pan: { enabled: false }, zoom: { enabled: false }, @@ -95,9 +96,11 @@ window.graphTool = (containerId, options) => { lastArrow: { size: 7 }, straightFirst: false, straightLast: false, - fixed: true + fixed: true, + tabindex: '', + aria: { enabled: true, hidden: true, live: 'off' } }, - grid: { majorStep: [gt.snapSizeX, gt.snapSizeY] }, + grid: { majorStep: [gt.snapSizeX, gt.snapSizeY], aria: { enabled: true, hidden: true, live: 'off' } }, keyboard: { enabled: true, dx: gt.snapSizeX, @@ -116,6 +119,14 @@ window.graphTool = (containerId, options) => { const setupBoard = () => { gt.board = JXG.JSXGraph.initBoard(`${containerId}_graph`, cfgOptions); + + const descriptionSpan = document.createElement('span'); + descriptionSpan.id = `${containerId}_description`; + descriptionSpan.classList.add('visually-hidden'); + descriptionSpan.textContent = options.ariaDescription ?? 'Interactively graph objects'; + gt.board.containerObj.after(descriptionSpan); + gt.board.containerObj.setAttribute('aria-describedby', descriptionSpan.id); + gt.board.suspendUpdate(); // Move the axes defining points to the end so that the arrows go to the board edges. @@ -198,7 +209,7 @@ window.graphTool = (containerId, options) => { } if (this.visProp.useunicodeminus) labelText = labelText.replace(/-/g, '\u2212'); - return addTeXDelims ?? this.visProp.label.usemathjax ? `\\(${labelText}\\)` : labelText; + return (addTeXDelims ?? this.visProp.label.usemathjax) ? `\\(${labelText}\\)` : labelText; }; gt.board.defaultAxes.x.defaultTicks.generateLabelText = generateLabelText; @@ -255,6 +266,113 @@ window.graphTool = (containerId, options) => { gt.board.highlightInfobox = (_x, _y, el) => gt.board.highlightCustomInfobox('', el); if (!gt.isStatic) { + // FIXME: Delete this after upgrading to the next version of JSXGraph as this has been fixed in their + // develop branch and will be in that release. + // This is a mess to work around an issue with JSXGraph versions 1.11.1 or later. Their keyDownListener + // calls preventDefault on the keydown event when shift-tab is pressed. That prevents keyboard focus from + // moving backward in the tab order. So this removes the JSXGraph keyboard event handlers, then overrides + // the board's keyDownListener with essentially the same code with the exception that when the tab key is + // pressed, preventDefault is not called. Then the keyboard event listeners are added back, using this + // keydownListener. + gt.board.removeKeyboardEventHandlers(); + gt.board.keyDownListener = function (evt) { + const id_node = evt.target.id; + let done = true; + + if (!this.attr.keyboard.enabled || id_node === '' || evt.keyCode === 9) return false; + + const doc = this.containerObj.shadowRoot || document; + if (doc.activeElement) { + if (doc.activeElement.tagName === 'INPUT' || doc.activeElement.tagName === 'textarea') return false; + } + + const id = id_node.replace(this.containerObj.id + '_', ''); + const el = this.select(id); + + if ( + (JXG.evaluate(this.attr.keyboard.panshift) && evt.shiftKey) || + (JXG.evaluate(this.attr.keyboard.panctrl) && evt.ctrlKey) + ) { + const doZoom = JXG.evaluate(this.attr.zoom.enabled) === true; + if (evt.keyCode === 38) this.clickUpArrow(); + else if (evt.keyCode === 40) this.clickDownArrow(); + else if (evt.keyCode === 37) this.clickLeftArrow(); + else if (evt.keyCode === 39) this.clickRightArrow(); + else if (doZoom && evt.keyCode === 171) this.zoomIn(); + else if (doZoom && evt.keyCode === 173) this.zoomOut(); + else if (doZoom && evt.keyCode === 79) this.zoom100(); + else done = false; + } else if (!evt.shiftKey && !evt.ctrlKey) { + let dx = JXG.evaluate(this.attr.keyboard.dx) / this.unitX; + let dy = JXG.evaluate(this.attr.keyboard.dy) / this.unitY; + if (JXG.exists(el.visProp)) { + if ( + JXG.exists(el.visProp.snaptogrid) && + el.visProp.snaptogrid && + el.evalVisProp('snapsizex') && + el.evalVisProp('snapsizey') + ) { + const res = el.getSnapSizes(); + dx = res[0]; + dy = res[1]; + } else if ( + JXG.exists(el.visProp.attracttogrid) && + el.visProp.attracttogrid && + el.evalVisProp('attractordistance') && + el.evalVisProp('attractorunit') + ) { + let sX = 1.1 * el.evalVisProp('attractordistance'); + let sY = sX; + if (el.evalVisProp('attractorunit') === 'screen') { + sX /= this.unitX; + sY /= this.unitX; + } + dx = Math.max(sX, dx); + dy = Math.max(sY, dy); + } + } + + let dir; + if (evt.keyCode === 38) dir = [0, dy]; + else if (evt.keyCode === 40) dir = [0, -dy]; + else if (evt.keyCode === 37) dir = [-dx, 0]; + else if (evt.keyCode === 39) dir = [dx, 0]; + else done = false; + + if ( + dir && + el.isDraggable && + el.visPropCalc.visible && + ((this.geonextCompatibilityMode && + (JXG.isPoint(el) || el.elementClass === JXG.OBJECT_CLASS_TEXT)) || + !this.geonextCompatibilityMode) && + !el.evalVisProp('fixed') + ) { + this.mode = this.BOARD_MODE_DRAG; + if (JXG.exists(el.coords)) { + const actPos = el.coords.usrCoords.slice(1); + dir[0] += actPos[0]; + dir[1] += actPos[1]; + el.setPosition(JXG.COORDS_BY_USER, dir); + this.updateInfobox(el); + } else { + this.displayInfobox(false); + el.setPositionDirectly(JXG.COORDS_BY_USER, dir, [0, 0]); + } + + this.triggerEventHandlers(['keymove', 'move'], [evt, this.mode]); + el.triggerEventHandlers(['keydrag', 'drag'], [evt]); + this.mode = this.BOARD_MODE_NONE; + } + } + + this.update(); + + if (done && JXG.exists(evt.preventDefault)) evt.preventDefault(); + return done; + }; + gt.board.addKeyboardEventHandlers(); + gt.graphContainer.tabIndex = -1; gt.board.containerObj.tabIndex = -1; @@ -453,10 +571,10 @@ window.graphTool = (containerId, options) => { } else if (e.key === 'Escape' && gt.activeTool !== gt.selectTool) { // Escape deactivates any active tool except the select tool. gt.selectTool.activate(); - } else if (e.key === 'Delete' && e.ctrlKey) { + } else if ((e.key === 'Delete' || e.key === 'Backspace') && e.ctrlKey) { // If the Ctrl-Delete is pressed, then ask to delete all objects. gt.clearAll(); - } else if (e.key === 'Delete' && gt.activeTool === gt.selectTool) { + } else if ((e.key === 'Delete' || e.key === 'Backspace') && gt.activeTool === gt.selectTool) { // If the select tool is active and Delete is pressed, then ask to delete the selected object. gt.deleteSelected(); } @@ -584,11 +702,7 @@ window.graphTool = (containerId, options) => { newContent.classList.add('gt-message-content'); setTimeout(() => newContent.classList.add('gt-message-content', 'gt-message-fade')); - if (window.MathJax) { - MathJax.startup.promise = MathJax.startup.promise.then(() => - MathJax.typesetPromise([newContent]) - ); - } + if (window.MathJax) MathJax.typesetPromise?.([newContent]); } resolve(); @@ -596,15 +710,20 @@ window.graphTool = (containerId, options) => { }, 100); }); + let currentContent = ''; + gt.setMessageText = (content) => { if (gt.confirmationActive || !gt.helpEnabled) return; const newMessage = content instanceof Array ? content.join(' ') : content; if (newMessage) { + if (currentContent === newMessage) return; + currentContent = newMessage; const par = document.createElement('p'); par.textContent = newMessage; gt.setMessageContent(par); } else { + currentContent = ''; gt.setMessageContent(); } }; @@ -662,6 +781,14 @@ window.graphTool = (containerId, options) => { gt.pointRegexp = /\( *(-?[0-9]*(?:\.[0-9]*)?), *(-?[0-9]*(?:\.[0-9]*)?) *\)/g; + gt.pointAria = { + enabled: true, + label: (p) => `point at ${p.X()}, ${p.Y()}`, + roledescription: 'point', + live: 'assertive', + atomic: true + }; + // This returns true if the points p1, p2, and p3 are colinear. // Note that p1 must be an array of two numbers, and p2 and p3 must be JSXGraph points. gt.areColinear = (p1, p2, p3) => { @@ -798,7 +925,8 @@ window.graphTool = (containerId, options) => { const point = gt.board.create('point', [gt.snapRound(x, gt.snapSizeX), gt.snapRound(y, gt.snapSizeY)], { snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, - ...gt.definingPointAttributes + aria: gt.pointAria, + ...gt.definingPointAttributes() }); point.setAttribute({ snapToGrid: true }); if (!gt.isStatic) { @@ -1399,12 +1527,8 @@ window.graphTool = (containerId, options) => { helpText() { return (gt.selectedObj && gt.selectedObj.supportsSolidDash) || (gt.activeTool && gt.activeTool.supportsSolidDash) - ? 'Use the ' + - '\\(\\rule[3px]{34px}{2px}\\) or ' + - '\\(\\rule[3px]{3px}{2px}' + - '\\hspace{4px}\\rule[3px]{4px}{2px}'.repeat(3) + - '\\hspace{4px}\\rule[3px]{3px}{2px}\\)' + - ' button or type s or d to make the selected object solid or dashed.' + ? 'Use the solid line or dashed line buttons or type s or d to ' + + 'make the selected object solid or dashed.' : ''; } } diff --git a/htdocs/js/GraphTool/intervaltools.js b/htdocs/js/GraphTool/intervaltools.js index 8839e82dcb..9946f7ae11 100644 --- a/htdocs/js/GraphTool/intervaltools.js +++ b/htdocs/js/GraphTool/intervaltools.js @@ -43,7 +43,41 @@ 0 ] ], - { fixed: true, highlight: false, strokeColor: gt.color.curve, strokeWidth: 4 } + { + fixed: true, + highlight: false, + strokeColor: gt.color.curve, + strokeWidth: 4, + tabindex: '', + aria: { + enabled: true, + label: () => { + const left = point1.X() < point2.X() ? point1 : point2; + const right = point1.X() < point2.X() ? point2 : point1; + const leftIsInf = gt.isNegInfX(left.X()); + const rightIsInf = gt.isPosInfX(right.X()); + return ( + 'interval that starts at ' + + (leftIsInf + ? '' + : left.getAttribute('fillColor') === 'transparent' + ? 'but does not include ' + : 'and includes ') + + (leftIsInf ? 'negative infinity' : left.X()) + + ' and ends at ' + + (rightIsInf + ? '' + : right.getAttribute('fillColor') === 'transparent' + ? 'but does not include ' + : 'and includes ') + + (rightIsInf ? 'infinity' : right.X()) + ); + }, + roledescription: 'interval', + live: 'assertive', + atomic: true + } + } ); // Redefine the segment's hasPoint method to return true if either of the end points has the @@ -310,7 +344,9 @@ strokeWidth: 4, strokeColor: 'transparent', highlight: false, - lastArrow: { type: 2, size: 4 } + lastArrow: { type: 2, size: 4 }, + tabindex: '', + aria: { enabled: true, hidden: true, live: 'off' } } ); this.definingPts[index].arrow.rendNodeTriangleEnd.setAttribute('fill', gt.color.curve); @@ -361,7 +397,20 @@ snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, ...this.definingPointAttributes(), - ...this.maybeBracketAttributes() + ...this.maybeBracketAttributes(), + aria: { + enabled: true, + label: (p) => + gt.isNegInfX(p.X()) + ? 'end point negative infinity' + : gt.isPosInfX(p.X()) + ? 'end point infinity' + : (p.getAttribute('fillColor') === 'transparent' ? 'excluded' : 'included') + + ` end point ${p.X()}`, + roledescription: 'end point', + live: 'assertive', + atomic: true + } }); point.setAttribute({ snapToGrid: true }); @@ -407,7 +456,9 @@ display: 'internal', fixed: true, strokeColor: gt.color.focusCurve, - highlightStrokeColor: gt.color.pointHighlightDarker + highlightStrokeColor: gt.color.pointHighlightDarker, + tabindex: '', + aria: { enabled: true, hidden: true, live: 'off' } } ); @@ -454,7 +505,8 @@ highlightStrokeWidth: 3, highlightStrokeColor: gt.color.pointHighlightDarker, // highlightFillColor is gt.color.pointHighlight if not included. - highlightFillColor: gt.color.pointHighlightDarker + highlightFillColor: gt.color.pointHighlightDarker, + tabindex: gt.isStatic ? -1 : 0 }; } @@ -506,9 +558,22 @@ snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, withLabel: false, - ...gt.graphObjectTypes.interval.maybeBracketAttributes() + ...gt.graphObjectTypes.interval.maybeBracketAttributes(), + tabindex: '', + aria: { + enabled: true, + label: (p) => + gt.isNegInfX(p.X()) + ? 'end point negative infinity' + : gt.isPosInfX(p.X()) + ? 'end point infinity' + : (p.getAttribute('fillColor') === 'transparent' ? 'excluded' : 'included') + + ` end point ${p.X()}`, + roledescription: 'end point', + live: 'assertive', + atomic: true + } }); - this.point1.setAttribute({ fixed: true }); if (gt.options.useBracketEnds) { const point = this.point1; @@ -539,7 +604,9 @@ fixed: true, highlight: false, strokeColor: gt.color.underConstructionFixed, - cssStyle: 'cursor:none;font-weight:900' + cssStyle: 'cursor:none;font-weight:900', + tabindex: '', + aria: { enabled: true, hidden: true, live: 'off' } } ); } @@ -653,7 +720,22 @@ fillColor: gt.toolTypes.IncludeExcludePointTool.include ? gt.color.underConstruction : 'transparent', - ...gt.graphObjectTypes.interval.maybeBracketAttributes() + ...gt.graphObjectTypes.interval.maybeBracketAttributes(), + tabindex: 0, + aria: { + enabled: true, + label: (p) => + gt.isNegInfX(p.X()) + ? 'end point negative infinity' + : gt.isPosInfX(p.X()) + ? 'end point infinity' + : (p.getAttribute('fillColor') === 'transparent' + ? 'excluded' + : 'included') + ` end point ${p.X()}`, + roledescription: 'end point', + live: 'assertive', + atomic: true + } }); if (gt.options.useBracketEnds) { @@ -683,7 +765,9 @@ fixed: true, highlight: false, strokeColor: gt.color.underConstruction, - cssStyle: 'cursor:none;font-weight:900' + cssStyle: 'cursor:none;font-weight:900', + tabindex: '', + aria: { enabled: true, hidden: true, live: 'off' } } ); } @@ -729,7 +813,42 @@ fixed: true, strokeWidth: 5, strokeColor: gt.color.underConstruction, - highlight: false + highlight: false, + tabindex: '', + aria: { + enabled: true, + label: () => { + const left = + this.point1 && this.point1.X() < this.hlObjs.hl_point.X() + ? this.point1 + : this.hlObjs.hl_point; + const right = + !this.point1 || this.point1.X() < this.hlObjs.hl_point.X() + ? this.hlObjs.hl_point + : this.point1; + const leftIsInf = gt.isNegInfX(left.X()); + const rightIsInf = gt.isPosInfX(right.X()); + return ( + 'interval that starts at ' + + (leftIsInf + ? '' + : left.getAttribute('fillColor') === 'transparent' + ? 'but does not include ' + : 'and includes ') + + (leftIsInf ? 'negative infinity' : left.X()) + + ' and ends at ' + + (rightIsInf + ? '' + : right.getAttribute('fillColor') === 'transparent' + ? 'but does not include ' + : 'and includes ') + + (rightIsInf ? 'infinity' : right.X()) + ); + }, + roledescription: 'interval', + live: 'assertive', + atomic: true + } } ); // The default layer for lines (of which arrows are a part) is 7. @@ -777,7 +896,9 @@ strokeWidth: 5, strokeColor: 'transparent', highlight: false, - lastArrow: { type: 2, size: 4 } + lastArrow: { type: 2, size: 4 }, + tabindex: '', + aria: { enabled: true, hidden: true, live: 'off' } } ); this.hlObjs.hl_arrow.rendNodeTriangleEnd.setAttribute( diff --git a/htdocs/js/GraphTool/linetool.js b/htdocs/js/GraphTool/linetool.js index acf6cb03c1..6660399315 100644 --- a/htdocs/js/GraphTool/linetool.js +++ b/htdocs/js/GraphTool/linetool.js @@ -16,9 +16,19 @@ fixed: true, highlight: false, strokeColor: gt.color.curve, - dash: solid ? 0 : 2 + dash: solid ? 0 : 2, + tabindex: '' }) ); + this.baseObj.setAttribute({ + aria: { + enabled: true, + label: this.constructor.ariaLabel, + roledescription: this.constructor.strId, + live: 'assertive', + atomic: true + } + }); this.definingPts.push(point1, point2); this.focusPoint = point1; } @@ -38,7 +48,7 @@ return gt.sign(JXG.Math.innerProduct(point, this.baseObj.stdform)); } - hasPoint(point) { + onBoundary(point) { return ( Math.abs(JXG.Math.innerProduct(point, this.baseObj.stdform)) / Math.sqrt(this.baseObj.stdform[1] ** 2 + this.baseObj.stdform[2] ** 2) < @@ -46,6 +56,13 @@ ); } + static ariaLabel(l) { + return ( + (l.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` line through ${l.point1.X()}, ${l.point1.Y()}, and ${l.point2.X()}, ${l.point2.Y()}` + ); + } + static restore(string) { let pointData = gt.pointRegexp.exec(string); const points = []; @@ -115,7 +132,9 @@ highlight: false, snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, - withLabel: false + withLabel: false, + tabindex: 0, + aria: gt.pointAria }); this.hlObjs.hl_point.rendNode.focus(); } @@ -128,7 +147,15 @@ fixed: true, strokeColor: gt.color.underConstruction, highlight: false, - dash: gt.drawSolid ? 0 : 2 + dash: gt.drawSolid ? 0 : 2, + tabindex: '', + aria: { + enabled: true, + label: gt.graphObjectTypes[this.object].ariaLabel, + roledescription: this.object, + live: 'assertive', + atomic: true + } }); } @@ -151,7 +178,9 @@ highlight: false, snapToGrid: true, snapSizeX: gt.snapSizeX, - snapSizeY: gt.snapSizeY + snapSizeY: gt.snapSizeY, + tabindex: '', + aria: gt.pointAria }); this.point1.setAttribute({ fixed: true }); @@ -188,7 +217,7 @@ const point1 = this.point1; delete this.point1; - point1.setAttribute(gt.definingPointAttributes); + point1.setAttribute(gt.definingPointAttributes()); point1.on('down', () => gt.onPointDown(point1)); point1.on('up', () => gt.onPointUp(point1)); diff --git a/htdocs/js/GraphTool/parabolatool.js b/htdocs/js/GraphTool/parabolatool.js index c1da9bb0da..b8a9ea6df9 100644 --- a/htdocs/js/GraphTool/parabolatool.js +++ b/htdocs/js/GraphTool/parabolatool.js @@ -73,7 +73,18 @@ strokeWidth: 2, highlight: false, strokeColor: color ? color : gt.color.underConstruction, - dash: solid ? 0 : 2 + dash: solid ? 0 : 2, + tabindex: '', + aria: { + enabled: true, + label: (p) => + (p.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` vertical parabola with vertex ${vertex.X()}, ${vertex.Y()} and ` + + `passing through the point ${point.X()}, ${point.Y()}`, + roledescription: 'vertical parabola', + live: 'assertive', + atomic: true + } } ); else @@ -91,7 +102,18 @@ strokeWidth: 2, highlight: false, strokeColor: color ? color : gt.color.underConstruction, - dash: solid ? 0 : 2 + dash: solid ? 0 : 2, + tabindex: '', + aria: { + enabled: true, + label: (p) => + (p.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` horizontal parabola with vertex ${vertex.X()}, ${vertex.Y()} and ` + + `passing through the point ${point.X()}, ${point.Y()}`, + roledescription: 'horizontal parabola', + live: 'assertive', + atomic: true + } } ); } @@ -161,7 +183,9 @@ snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, highlight: false, - withLabel: false + withLabel: false, + tabindex: 0, + aria: gt.pointAria }); this.hlObjs.hl_point.rendNode.focus(); } @@ -197,7 +221,9 @@ highlight: false, snapToGrid: true, snapSizeX: gt.snapSizeX, - snapSizeY: gt.snapSizeY + snapSizeY: gt.snapSizeY, + tabindex: '', + aria: gt.pointAria }); this.vertex.setAttribute({ fixed: true }); @@ -237,7 +263,7 @@ const vertex = this.vertex; delete this.vertex; - vertex.setAttribute(gt.definingPointAttributes); + vertex.setAttribute(gt.definingPointAttributes()); vertex.on('down', () => gt.onPointDown(vertex)); vertex.on('up', () => gt.onPointUp(vertex)); diff --git a/htdocs/js/GraphTool/pointtool.js b/htdocs/js/GraphTool/pointtool.js index 2420b41a24..4b76c39200 100644 --- a/htdocs/js/GraphTool/pointtool.js +++ b/htdocs/js/GraphTool/pointtool.js @@ -22,7 +22,9 @@ strokeColor: gt.color.curve, fixed: gt.isStatic, highlightStrokeColor: gt.color.underConstruction, - highlightFillColor: gt.color.pointHighlight + highlightFillColor: gt.color.pointHighlight, + tabindex: gt.isStatic ? -1 : 0, + aria: gt.pointAria }) ); @@ -159,7 +161,9 @@ highlight: false, snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, - withLabel: false + withLabel: false, + tabindex: 0, + aria: gt.pointAria }); this.hlObjs.hl_point.rendNode.focus(); } diff --git a/htdocs/js/GraphTool/quadratictool.js b/htdocs/js/GraphTool/quadratictool.js index a5a8d94203..c25355f5cd 100644 --- a/htdocs/js/GraphTool/quadratictool.js +++ b/htdocs/js/GraphTool/quadratictool.js @@ -12,7 +12,7 @@ constructor(point1, point2, point3, solid) { for (const point of [point1, point2, point3]) { - point.setAttribute(gt.definingPointAttributes); + point.setAttribute(gt.definingPointAttributes()); if (!gt.isStatic) { point.on('down', () => gt.onPointDown(point)); point.on('up', () => gt.onPointUp(point)); @@ -82,7 +82,18 @@ strokeWidth: 2, highlight: false, strokeColor: color ? color : gt.color.underConstruction, - dash: solid ? 0 : 2 + dash: solid ? 0 : 2, + tabindex: '', + aria: { + enabled: true, + label: (q) => + (q.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` quadratic passing through ${point1.X()}, ${point1.Y()}, and ` + + `${point2.X()}, ${point2.Y()}, and ${point3.X()}, ${point3.Y()}`, + roledescription: 'quadratic', + live: 'assertive', + atomic: true + } } ); } @@ -133,7 +144,9 @@ size: 2, snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, - withLabel: false + withLabel: false, + tabindex: '', + aria: gt.pointAria } ); point.setAttribute({ snapToGrid: true }); @@ -311,7 +324,9 @@ snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, highlight: false, - withLabel: false + withLabel: false, + tabindex: 0, + aria: gt.pointAria }); this.hlObjs.hl_point.rendNode.focus(); } @@ -343,7 +358,18 @@ fixed: true, strokeColor: gt.color.underConstruction, highlight: false, - dash: gt.drawSolid ? 0 : 2 + dash: gt.drawSolid ? 0 : 2, + tabindex: '', + aria: { + enabled: true, + label: (l) => + (l.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` line through ${l.point1.X()}, ${l.point1.Y()}, ` + + `and ${l.point2.X()}, ${l.point2.Y()}`, + roledescription: 'line', + live: 'assertive', + atomic: true + } }); } diff --git a/htdocs/js/GraphTool/quadrilateral.js b/htdocs/js/GraphTool/quadrilateral.js index 9ec263626b..4ad9cf554f 100644 --- a/htdocs/js/GraphTool/quadrilateral.js +++ b/htdocs/js/GraphTool/quadrilateral.js @@ -12,7 +12,7 @@ constructor(point1, point2, point3, point4, solid) { for (const point of [point1, point2, point3, point4]) { - point.setAttribute(gt.definingPointAttributes); + point.setAttribute(gt.definingPointAttributes()); if (!gt.isStatic) { point.on('down', () => gt.onPointDown(point)); point.on('up', () => gt.onPointUp(point)); @@ -94,7 +94,7 @@ return -1; } - onBoundary(point, aVal, _from) { + onBoundary(point, aVal) { if (this.fillCmp(point) != aVal) return true; for (const border of this.baseObj.borders) { @@ -284,7 +284,9 @@ size: 2, snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, - withLabel: false + withLabel: false, + tabindex: '', + aria: gt.pointAria } ); point.setAttribute({ snapToGrid: true }); @@ -344,7 +346,9 @@ highlight: false, snapToGrid: true, snapSizeX: gt.snapSizeX, - snapSizeY: gt.snapSizeY + snapSizeY: gt.snapSizeY, + tabindex: '', + aria: gt.pointAria }); this.point1.setAttribute({ fixed: true }); @@ -439,7 +443,7 @@ if (count == 0) { if (vDir != 0) ++times; count = times; - [hDir, vDir] = [!!hDir ? 0 : -vDir, !!vDir ? 0 : hDir]; + [hDir, vDir] = [hDir ? 0 : -vDir, vDir ? 0 : hDir]; } newX += hDir * gt.snapSizeX; newY += vDir * gt.snapSizeY; @@ -544,7 +548,9 @@ highlight: false, snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, - withLabel: false + withLabel: false, + tabindex: 0, + aria: gt.pointAria }); this.hlObjs.hl_point.rendNode.focus(); } @@ -584,7 +590,18 @@ highlight: false, dash: gt.drawSolid ? 0 : 2, straightFirst: false, - straightLast: false + straightLast: false, + tabindex: '', + aria: { + enabled: true, + label: (l) => + (l.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` line segment between ${l.point1.X()}, ${l.point1.Y()} ` + + `and ${l.point2.X()}, ${l.point2.Y()}`, + roledescription: 'line', + live: 'assertive', + atomic: true + } }); } diff --git a/htdocs/js/GraphTool/segments.js b/htdocs/js/GraphTool/segments.js index 48e2c2fefc..ed472d6462 100644 --- a/htdocs/js/GraphTool/segments.js +++ b/htdocs/js/GraphTool/segments.js @@ -1,4 +1,4 @@ -/* global graphTool */ +/* global graphTool, JXG */ 'use strict'; @@ -59,6 +59,13 @@ 0.5 / Math.sqrt(gt.board.unitX * gt.board.unitY) ); } + + static ariaLabel(l) { + return ( + (l.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` segment between ${l.point1.X()}, ${l.point1.Y()} and ${l.point2.X()}, ${l.point2.Y()}` + ); + } }; }, @@ -97,6 +104,14 @@ super(point1, point2, solid); this.baseObj.setArrow(false, { type: 1, size: 4 }); } + + static ariaLabel(l) { + return ( + (l.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` vector with initial point ${l.point1.X()}, ${l.point1.Y()} ` + + `and terminal point ${l.point2.X()}, ${l.point2.Y()}` + ); + } }; }, diff --git a/htdocs/js/GraphTool/sinewavetool.js b/htdocs/js/GraphTool/sinewavetool.js index 057559d9f5..b7ec48d607 100644 --- a/htdocs/js/GraphTool/sinewavetool.js +++ b/htdocs/js/GraphTool/sinewavetool.js @@ -12,7 +12,7 @@ constructor(shiftPoint, periodPoint, amplitudePoint, solid) { for (const point of [shiftPoint, periodPoint, amplitudePoint]) { - point.setAttribute(gt.definingPointAttributes); + point.setAttribute(gt.definingPointAttributes()); if (!gt.isStatic) { point.on('down', () => gt.onPointDown(point)); point.on('up', () => gt.onPointUp(point)); @@ -104,7 +104,25 @@ strokeWidth: 2, highlight: false, strokeColor: color ? color : gt.color.underConstruction, - dash: solid ? 0 : 2 + dash: solid ? 0 : 2, + tabindex: '', + aria: { + enabled: true, + label: (s) => + (s.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` sine wave shifted ${Math.abs(point.X())} units to the ${ + point.X() < 0 ? 'left' : 'right' + }, shifted ${Math.abs(point.Y())} units ${ + point.X() < 0 ? 'downward' : 'upward' + }, with period ${ + (2 * Math.PI) / Math.abs(period()) + }, with amplitude ${Math.abs(amplitude())}` + + (period() < 0 ? ', sine wave horizontally reflected' : '') + + (amplitude() < 0 ? ', sine wave vertically reflected' : ''), + roledescription: 'sine wave', + live: 'assertive', + atomic: true + } } ); } @@ -199,7 +217,24 @@ size: 2, snapSizeX: periodPoint ? 1e-10 : gt.snapSizeX, snapSizeY: shiftPoint && !periodPoint ? 1e-10 : gt.snapSizeY, - withLabel: false + withLabel: false, + tabindex: '', + aria: { + enabled: true, + label: (p) => + periodPoint + ? `sine wave amplitude ${Math.abs(p.Y() - shiftPoint.Y())}` + + (p.Y() > shiftPoint.Y() ? '' : ', sine wave vertically reflected') + : shiftPoint + ? `sine wave period ${Math.abs(p.X() - shiftPoint.X())}` + + (p.X() > shiftPoint.X() ? '' : ', sine wave horizontally reflected') + : `sine wave shifted ${Math.abs(p.X())} units to the ${ + p.X() < 0 ? 'left' : 'right' + }, and shifted ${Math.abs(p.Y())} units ${p.Y() < 0 ? 'downward' : 'upward'}`, + roledescription: () => (periodPoint ? 'amplitude' : shiftPoint ? 'period' : 'shift'), + live: 'assertive', + atomic: true + } }); point.setAttribute({ snapToGrid: true }); @@ -376,7 +411,29 @@ snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, highlight: false, - withLabel: false + withLabel: false, + tabindex: 0, + aria: { + enabled: true, + label: (p) => + this.periodPoint + ? `sine wave amplitude ${Math.abs(p.Y() - this.shiftPoint.Y())}` + + (p.Y() > this.shiftPoint.Y() ? '' : ', sine wave vertically reflected') + : this.shiftPoint + ? `sine wave period ${Math.abs(p.X() - this.shiftPoint.X())}` + + (p.X() > this.shiftPoint.X() + ? '' + : ', sine wave horizontally reflected') + : `sine wave shifted ${Math.abs(p.X())} units to the ${ + p.X() < 0 ? 'left' : 'right' + }, and shifted ${Math.abs(p.Y())} units ${ + p.Y() < 0 ? 'downward' : 'upward' + }`, + roledescription: () => + this.periodPoint ? 'amplitude' : this.shiftPoint ? 'period' : 'shift', + live: 'assertive', + atomic: true + } }); this.hlObjs.hl_point.rendNode.focus(); } diff --git a/htdocs/js/GraphTool/triangle.js b/htdocs/js/GraphTool/triangle.js index e68f15aea6..a8e95948b3 100644 --- a/htdocs/js/GraphTool/triangle.js +++ b/htdocs/js/GraphTool/triangle.js @@ -12,7 +12,7 @@ constructor(point1, point2, point3, solid) { for (const point of [point1, point2, point3]) { - point.setAttribute(gt.definingPointAttributes); + point.setAttribute(gt.definingPointAttributes()); if (!gt.isStatic) { point.on('down', () => gt.onPointDown(point)); point.on('up', () => gt.onPointUp(point)); @@ -82,7 +82,7 @@ return -1; } - onBoundary(point, aVal, _from) { + onBoundary(point, aVal) { if (this.fillCmp(point) != aVal) return true; for (const border of this.baseObj.borders) { @@ -122,7 +122,7 @@ } static createPolygon(points, solid, color) { - return gt.board.create('polygon', points, { + const polygon = gt.board.create('polygon', points, { highlight: false, fillOpacity: 0, fixed: true, @@ -132,8 +132,31 @@ fixed: true, strokeColor: color ? color : gt.color.underConstruction, dash: solid ? 0 : 2 + }, + tabindex: '', + aria: { + enabled: true, + label: (t) => + (t.borders[0].getAttribute('dash') == 0 ? 'solid ' : 'dashed ') + + (t.vertices.length === 4 + ? 'triangle' + : t.vertices.length == 5 + ? 'quadrilateral' + : 'polygon') + + ' with vertices ' + + t.vertices + .slice(0, -1) + .map((p) => `${p.X()}, ${p.Y()}`) + .join(', and '), + roledescription: 'polygon', + live: 'assertive', + atomic: true } }); + for (const border of polygon.borders) { + border.setAttribute({ tabindex: '', aria: { enabled: true, hidden: true, live: 'off' } }); + } + return polygon; } // Prevent a point from being moved off the board by a drag. If one or two other points are @@ -227,7 +250,9 @@ size: 2, snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, - withLabel: false + withLabel: false, + tabindex: '', + aria: gt.pointAria } ); point.setAttribute({ snapToGrid: true }); @@ -284,7 +309,9 @@ highlight: false, snapToGrid: true, snapSizeX: gt.snapSizeX, - snapSizeY: gt.snapSizeY + snapSizeY: gt.snapSizeY, + tabindex: '', + aria: gt.pointAria }); this.point1.setAttribute({ fixed: true }); @@ -414,7 +441,9 @@ highlight: false, snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, - withLabel: false + withLabel: false, + tabindex: 0, + aria: gt.pointAria }); this.hlObjs.hl_point.rendNode.focus(); } @@ -446,7 +475,18 @@ highlight: false, dash: gt.drawSolid ? 0 : 2, straightFirst: false, - straightLast: false + straightLast: false, + tabindex: '', + aria: { + enabled: true, + label: (l) => + (l.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` line segment between ${l.point1.X()}, ${l.point1.Y()} ` + + `and ${l.point2.X()}, ${l.point2.Y()}`, + roledescription: 'line', + live: 'assertive', + atomic: true + } }); } diff --git a/htdocs/js/ImageView/imageview.js b/htdocs/js/ImageView/imageview.js index 2d99e2b985..2a489ee262 100644 --- a/htdocs/js/ImageView/imageview.js +++ b/htdocs/js/ImageView/imageview.js @@ -31,6 +31,12 @@ modal.setAttribute('aria-label', 'image view dialog'); modal.tabIndex = -1; + // Force the dialog into light mode. This is needed for a webwork2 page in dark mode since the dialog is outside + // of the problem content. At least until PG is updated to honor dark mode. Further discussion on this will + // also be needed at that time since many images have transparent backgrounds that will not work with a dark + // background. + modal.dataset.bsTheme = 'light'; + const dialog = document.createElement('div'); dialog.classList.add('modal-dialog'); @@ -42,7 +48,7 @@ const zoomInButton = document.createElement('button'); zoomInButton.type = 'button'; - zoomInButton.classList.add('btn', 'btn-outline-secondary', 'btn-sm', 'zoom-in'); + zoomInButton.classList.add('btn', 'btn-outline-secondary', 'btn-sm'); zoomInButton.setAttribute('aria-label', 'zoom in'); const zoomInSVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); @@ -74,8 +80,8 @@ const zoomOutButton = document.createElement('button'); zoomOutButton.type = 'button'; - zoomOutButton.classList.add('btn', 'btn-outline-secondary', 'btn-sm', 'zoom-in'); - zoomOutButton.setAttribute('aria-label', 'zoom in'); + zoomOutButton.classList.add('btn', 'btn-outline-secondary', 'btn-sm'); + zoomOutButton.setAttribute('aria-label', 'zoom out'); const zoomOutSVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); zoomOutSVG.setAttribute('width', 16); @@ -219,7 +225,7 @@ if (graphDiv) { graphDiv.style.width = width + 'px'; graphDiv.style.height = height + 'px'; - this.dispatchEvent(new Event('resized.imageview')); + graphDiv.dispatchEvent(new Event('resized.imageview')); } // Re-position the modal. @@ -312,7 +318,7 @@ backdrop.style.opacity = '0.2'; }); modal.addEventListener('hidden.bs.modal', () => { - if (imgType == 'div') this.dispatchEvent(new Event('hidden.imageview')); + if (graphDiv) graphDiv.dispatchEvent(new Event('hidden.imageview')); bsModal.dispose(); modal.remove(); window.removeEventListener('resize', onWinResize); diff --git a/htdocs/js/Knowls/knowl.js b/htdocs/js/Knowls/knowl.js index 496b42a7f6..0894f988f9 100644 --- a/htdocs/js/Knowls/knowl.js +++ b/htdocs/js/Knowls/knowl.js @@ -24,6 +24,10 @@ knowl.knowlModal.setAttribute('aria-labelledby', `${knowl.knowlModal.id}-title`); knowl.knowlModal.setAttribute('aria-hidden', 'true'); + // Force the dialog into light mode. This is needed for a webwork2 page in dark mode since the dialog is + // outside of the problem content. At least until PG and the help files are updated to honor dark mode. + knowl.knowlModal.dataset.bsTheme = 'light'; + const knowlDialog = document.createElement('div'); knowlDialog.classList.add( 'knowl-dialog', @@ -85,9 +89,7 @@ setInnerHTML(knowlBody, knowl.dataset.knowlContents); // If we are using MathJax, then render math content. - if (window.MathJax) { - MathJax.startup.promise = MathJax.startup.promise.then(() => MathJax.typesetPromise([knowlBody])); - } + if (window.MathJax) MathJax.typesetPromise?.([knowlBody]); } else if (knowl.dataset.knowlUrl) { // Retrieve url content. fetch(knowl.dataset.knowlUrl) @@ -100,11 +102,7 @@ setInnerHTML(knowlBody, data); } // If we are using MathJax, then render math content. - if (window.MathJax) { - MathJax.startup.promise = MathJax.startup.promise.then(() => - MathJax.typesetPromise([knowlBody]) - ); - } + if (window.MathJax) MathJax.typesetPromise?.([knowlBody]); }) .catch((err) => { knowlBody.textContent = `ERROR: ${err}`; diff --git a/htdocs/js/MathQuill/mqeditor.js b/htdocs/js/MathQuill/mqeditor.js index 7faa181b2c..9df8ed63bd 100644 --- a/htdocs/js/MathQuill/mqeditor.js +++ b/htdocs/js/MathQuill/mqeditor.js @@ -9,8 +9,6 @@ // initialize MathQuill const MQ = MathQuill.getInterface(); - let toolbarEnabled = (localStorage.getItem('MQEditorToolbarEnabled') ?? 'true') === 'true'; - const setupMQInput = (mq_input) => { if (mq_input.dataset.mqEditorInitialized) return; mq_input.dataset.mqEditorInitialized = 'true'; @@ -50,6 +48,7 @@ .join(' '), rootsAreExponents: true, logsChangeBase: true, + useToolbar: true, maxDepth: 10 }; @@ -57,23 +56,7 @@ if (answerQuill.latexInput.dataset.mqOpts) Object.assign(cfgOptions, JSON.parse(answerQuill.latexInput.dataset.mqOpts)); - // The handlers and blurWithCursor options are set after - // the option merge to prevent them from being overridden. - cfgOptions.handlers = { - // Disable the toolbar when a text block is entered. - textBlockEnter: () => { - answerQuill.toolbar?.querySelectorAll('button').forEach((button) => (button.disabled = true)); - }, - // Re-enable the toolbar when a text block is exited. - textBlockExit: () => { - answerQuill.toolbar?.querySelectorAll('button').forEach((button) => (button.disabled = false)); - } - }; - - cfgOptions.blurWithCursor = (e) => - toolbarEnabled && - answerQuill.toolbar && - (e.relatedTarget?.closest('.quill-toolbar') || e.relatedTarget?.classList.contains('symbol-button')); + cfgOptions.handlers = {}; const latexEntryMode = input.classList.contains('latexentryfield'); @@ -276,11 +259,6 @@ // Trigger a button press when the enter key is pressed in an answer box. cfgOptions.handlers.enter = () => { - // Ensure that the toolbar and any open tooltips are removed. - answerQuill.toolbar?.tooltips.forEach((tooltip) => tooltip.dispose()); - answerQuill.toolbar?.remove(); - delete answerQuill.toolbar; - // For ww2 homework if the enter_key_submit button is found, then use that. // This Depends on $pg{options}{enterKey}. const enterKeySubmit = document.getElementById('enter_key_submit'); @@ -291,12 +269,6 @@ // If the enter_key_submit button is not found (it will not be present in tests), // then use the preview button. document.querySelector('input[name=previewAnswers]')?.click(); - - // For ww3 - const previewButtonId = answerQuill.textarea - .closest('[name=problemMainForm]') - ?.id.replace('problemMainForm', 'previewAnswers'); - if (previewButtonId) document.getElementById(previewButtonId)?.click(); }; input.after(answerQuill); @@ -306,274 +278,34 @@ answerQuill.textarea = answerQuill.querySelector('textarea'); - answerQuill.buttons = [ - { id: 'frac', latex: '/', tooltip: 'fraction (/)', icon: '\\frac{\\text{ }}{\\text{ }}' }, - { id: 'abs', latex: '|', tooltip: 'absolute value (|)', icon: '|\\text{ }|' }, - { id: 'sqrt', latex: '\\sqrt', tooltip: 'square root (sqrt)', icon: '\\sqrt{\\text{ }}' }, - { id: 'nthroot', latex: '\\root', tooltip: 'nth root (root)', icon: '\\sqrt[\\text{ }]{\\text{ }}' }, - { id: 'exponent', latex: '^', tooltip: 'exponent (^)', icon: '\\text{ }^\\text{ }' }, - ...(cfgOptions.logsChangeBase - ? [] - : [{ id: 'subscript', latex: '_', tooltip: 'subscript (_)', icon: '\\text{ }_\\text{ }' }]), - { id: 'infty', latex: '\\infty', tooltip: 'infinity (inf)', icon: '\\infty' }, - { id: 'pi', latex: '\\pi', tooltip: 'pi (pi)', icon: '\\pi' }, - { id: 'vert', latex: '\\vert', tooltip: 'such that (vert)', icon: '|' }, - { id: 'cup', latex: '\\cup', tooltip: 'union (union)', icon: '\\cup' }, - // { id: 'leq', latex: '\\leq', tooltip: 'less than or equal (<=)', icon: '\\leq' }, - // { id: 'geq', latex: '\\geq', tooltip: 'greater than or equal (>=)', icon: '\\geq' }, - { id: 'text', latex: '\\text', tooltip: 'text mode (")', icon: 'Tt' } - ]; - - const toolbarRemove = () => { - if (answerQuill.toolbar) { - const toolbar = answerQuill.toolbar; - delete answerQuill.toolbar; - toolbar.style.opacity = 0; - window.removeEventListener('resize', toolbar.setPosition); - window.removeEventListener('focus', toolbar.removeOnWindowRefocus); - toolbar.tooltips.forEach((tooltip) => tooltip.dispose()); - toolbar.addEventListener('transitionend', () => toolbar.remove(), { once: true }); - toolbar.addEventListener('transitioncancel', () => toolbar.remove(), { once: true }); - if (toolbarEnabled && document.activeElement !== answerQuill.textarea) answerQuill.mathField.blur(); - } - }; - - // Open the toolbar when the mathquill answer box gains focus. - answerQuill.textarea.addEventListener('focusin', () => { - if (!toolbarEnabled) return; - if (answerQuill.toolbar) return; - - answerQuill.toolbar = document.createElement('div'); - answerQuill.toolbar.tabIndex = -1; - answerQuill.toolbar.classList.add('quill-toolbar'); - answerQuill.toolbar.style.opacity = 0; - - answerQuill.toolbar.addEventListener('focusout', (e) => { - if ( - !document.hasFocus() || - (e.relatedTarget && - (e.relatedTarget.closest('.quill-toolbar') || - e.relatedTarget.classList.contains('symbol-button') || - e.relatedTarget === answerQuill.textarea)) || - (answerQuill.clearButton && e.relatedTarget === answerQuill.clearButton) - ) - return; - - toolbarRemove(); - }); - - // If the window is refocused after a blur, and the focus is not on the toolbar - // or the MathQuill input, then remove the toolbar. - answerQuill.toolbar.removeOnWindowRefocus = () => { - if ( - document.activeElement && - !document.activeElement.closest('.quill-toolbar') && - !document.activeElement.classList.contains('symbol-button') && - document.activeElement !== answerQuill.textarea - ) - toolbarRemove(); - }; - window.addEventListener('focus', answerQuill.toolbar.removeOnWindowRefocus); - - answerQuill.toolbar.tooltips = []; - - for (const buttonData of answerQuill.buttons) { - const button = document.createElement('button'); - button.type = 'button'; - button.id = `${buttonData.id}-${answerQuill.id}`; - button.classList.add('symbol-button', 'btn', 'btn-dark'); - button.dataset.latex = buttonData.latex; - button.dataset.bsToggle = 'tooltip'; - button.title = buttonData.tooltip; - const icon = document.createElement('span'); - icon.id = `icon-${buttonData.id}-${answerQuill.id}`; - icon.textContent = buttonData.icon; - icon.setAttribute('aria-hidden', 'true'); - button.append(icon); - answerQuill.toolbar.append(button); - - MQ.StaticMath(icon, { mouseEvents: false, tabbable: false }); - - answerQuill.toolbar.tooltips.push(new bootstrap.Tooltip(button, { placement: 'left' })); - - button.addEventListener('click', () => { - answerQuill.textarea.focus(); - answerQuill.mathField.cmd(button.dataset.latex); - }); - } - - const getNextFocusableElement = (currentElement) => { - const focusableElements = Array.from( - document.querySelectorAll( - 'a[href]:not([tabindex="-1"]),' + - 'button:not([tabindex="-1"]),' + - 'input:not([tabindex="-1"]),' + - 'textarea:not([tabindex="-1"]),' + - 'select:not([tabindex="-1"]),' + - 'details:not([tabindex="-1"]),' + - '[tabindex]:not([tabindex="-1"])' - ) - ); - - let currentIndex = focusableElements.indexOf(currentElement); - if (currentIndex === -1) return; - - for (const focusableElement of focusableElements.slice(currentIndex + 1)) { - if (!focusableElement.disabled && focusableElement.offsetParent !== null) return focusableElement; - } - }; - - answerQuill.toolbar.addEventListener('keydown', (e) => { - if (e.key === 'Escape') { - const nextFocusable = getNextFocusableElement(answerQuill.toolbar.lastElementChild); - toolbarRemove(); - nextFocusable?.focus(); - } - }); - - answerQuill.toolbar.setPosition = () => { - // Note that this must be kept in sync with css. Currently each symbol button has a fixed height (due - // to flex-shrink being 0) of 45px plus a 1px padding on the top and bottom plus a 1px margin on the top - // and bottom, giving a 49px total height for each symbol button . Also, the toolbar itself has a 2px - // border on the top and bottom, hence 4px is added to the end. These computations take into account - // that box-sizing is border-box. - const toolbarHeight = 49 * answerQuill.buttons.length + 4; - - const pageHeight = (() => { - const documentElHeight = document.documentElement.getBoundingClientRect().height; - if (window.innerHeight > documentElHeight) return window.innerHeight; - return documentElHeight; - })(); - - // Different positioning is needed when contained in a relatively positioned parent. - const relativeParent = (() => { - let parent = answerQuill.parentElement; - while (parent && parent !== document) { - const positionType = window.getComputedStyle(parent).position; - if (positionType === 'relative') return parent; - // If a fixed parent is encountered before a relative parent is encountered, - // that negates relative positioning. - if (positionType === 'fixed') return; - parent = parent.parentElement; - } - })(); - - if (relativeParent) { - // If contained in a relatively positioned parent, the toolbar needs - // to be positioned relative to that parent. - const pageWidth = (() => { - const documentElWidth = document.documentElement.getBoundingClientRect().width; - if (window.innerWidth > documentElWidth) return window.innerWidth; - return documentElWidth; - })(); - - const parentRect = relativeParent.getBoundingClientRect(); - answerQuill.toolbar.style.right = `${window.scrollX + parentRect.right + 10 - pageWidth}px`; - - const elRect = answerQuill.getBoundingClientRect(); - - if (window.scrollY + elRect.top + elRect.height / 2 < toolbarHeight / 2) { - answerQuill.toolbar.style.top = `-${window.scrollY + parentRect.top}px`; - answerQuill.toolbar.style.bottom = - toolbarHeight > pageHeight ? `${window.scrollY + parentRect.bottom - pageHeight}px` : null; - } else if (window.scrollY + elRect.top + elRect.height / 2 + toolbarHeight / 2 > pageHeight) { - answerQuill.toolbar.style.top = null; - answerQuill.toolbar.style.bottom = `${window.scrollY + parentRect.bottom - pageHeight}px`; - } else { - answerQuill.toolbar.style.top = `${ - elRect.top + elRect.height / 2 - toolbarHeight / 2 - parentRect.top - }px`; - answerQuill.toolbar.style.bottom = null; - } - } else { - // If not in a relatively positioned parent, the toolbar is positioned absolutely on the page. - if (toolbarHeight > pageHeight) { - answerQuill.toolbar.style.top = 0; - answerQuill.toolbar.style.height = '100%'; - } else { - const elRect = answerQuill.getBoundingClientRect(); - const top = window.scrollY + elRect.bottom - elRect.height / 2 - toolbarHeight / 2; - const bottom = top + toolbarHeight; - answerQuill.toolbar.style.top = `${ - top < 0 ? 0 : bottom > pageHeight ? pageHeight - toolbarHeight : top - }px`; - answerQuill.toolbar.style.height = null; - } - } - }; - - window.addEventListener('resize', answerQuill.toolbar.setPosition); - answerQuill.toolbar.setPosition(); - - answerQuill.after(answerQuill.toolbar); - setTimeout(() => { - if (answerQuill.toolbar) answerQuill.toolbar.style.opacity = 1; - }, 0); - }); - - // Add a context menu to toggle whether the toolbar is enabled or not. - answerQuill.addEventListener('contextmenu', (e) => { - e.preventDefault(); - - const container = document.createElement('div'); - container.classList.add('dropdown', 'd-inline-block'); - answerQuill.after(container); - - const hiddenLink = document.createElement('a'); - hiddenLink.classList.add('dropdown-toggle', 'd-none'); - hiddenLink.dataset.bsToggle = 'dropdown'; - hiddenLink.href = '#'; - container.append(hiddenLink); - - const menuEl = document.createElement('ul'); - menuEl.classList.add('dropdown-menu'); - const li = document.createElement('li'); - menuEl.append(li); - const action = document.createElement('a'); - action.classList.add('dropdown-item'); - action.href = '#'; - action.textContent = toolbarEnabled ? 'Disable Toolbar' : 'Enable Toolbar'; - li.append(action); - container.append(menuEl); - - const menu = new bootstrap.Dropdown(hiddenLink, { - reference: answerQuill, - offset: [answerQuill.offsetWidth, 0] - }); - menu.show(); - - hiddenLink.addEventListener('hidden.bs.dropdown', () => { - menu.dispose(); - menuEl.remove(); - container.remove(); - }); - - action.addEventListener( - 'click', - (e) => { - e.preventDefault(); - toolbarEnabled = !toolbarEnabled; - localStorage.setItem('MQEditorToolbarEnabled', toolbarEnabled); - if (!toolbarEnabled && answerQuill.toolbar) toolbarRemove(); - menu.hide(); - answerQuill.textarea.focus(); + if (!cfgOptions.logsChangeBase) { + answerQuill.mathField.options.addToolbarButtons( + { + id: 'subscript', + latex: '_', + tooltip: 'subscript (_)', + icon: '\\text{ }_\\text{ }' }, - { once: true } + 'exponent' ); - }); - - answerQuill.textarea.addEventListener('focusout', (e) => { - if ( - !document.hasFocus() || - (e.relatedTarget && - (e.relatedTarget.closest('.quill-toolbar') || - e.relatedTarget.classList.contains('symbol-button') || - (answerQuill.clearButton && e.relatedTarget === answerQuill.clearButton))) - ) - return; - - toolbarRemove(); - }); + } + + if (cfgOptions.includeIonButtons) { + answerQuill.mathField.options.addToolbarButtons([ + { + id: 'positiveion', + latex: '\\positiveion', + tooltip: 'positive ion', + icon: '\\text{ }^{\\text{ }+}' + }, + { + id: 'negativeion', + latex: '\\negativeion', + tooltip: 'negative ion', + icon: '\\text{ }^{\\text{ }-}' + } + ]); + } window.answerQuills[answerLabel] = answerQuill; diff --git a/htdocs/js/MathQuill/mqeditor.scss b/htdocs/js/MathQuill/mqeditor.scss index 6d780bdd38..870b99cbbf 100644 --- a/htdocs/js/MathQuill/mqeditor.scss +++ b/htdocs/js/MathQuill/mqeditor.scss @@ -96,79 +96,3 @@ input[type='text'].codeshard.mq-edit { } } } - -.quill-toolbar { - position: absolute; - font-size: 0.75em; - /*rtl:ignore*/ - direction: ltr; - display: flex; - flex-direction: column; - justify-content: start; - box-sizing: border-box; - border-radius: 4px; - border: 2px solid darkgray; - background-color: white; - /*rtl:ignore*/ - right: 10px; - z-index: 1001; - overflow-x: hidden; - overflow-y: auto; - scrollbar-width: thin; - opacity: 1; - transition: opacity 500ms ease; - - .symbol-button { - box-sizing: border-box; - text-align: center; - flex-shrink: 0; - padding: 3px; - margin: 2px; - display: block; - width: 45px; - height: 45px; - border-radius: 4px; - background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); - box-shadow: - inset 0 1px 0 rgba(255, 255, 255, 0.15), - 0 1px 1px rgba(0, 0, 0, 0.075); - - &:focus { - z-index: 9999; - } - - span[id^='icon-']:hover { - cursor: pointer; - } - - &:not([id^='text-mq-answer']) .mq-text-mode { - height: 10px; - width: 8px; - transform: translateY(2px); - background-color: skyblue !important; - } - - .mq-nthroot, - .mq-sup, - .mq-sub { - & > .mq-text-mode { - height: 6px; - width: 6px; - } - } - - .mq-sup > .mq-text-mode { - transform: translateY(2px); - } - - .mq-sub > .mq-text-mode { - transform: translateY(0); - } - - .mq-supsub { - height: 6px; - width: 6px; - margin-left: 2px; - } - } -} diff --git a/htdocs/js/MathView/mathview.js b/htdocs/js/MathView/mathview.js index 92e3acb10a..f193f2d6ac 100644 --- a/htdocs/js/MathView/mathview.js +++ b/htdocs/js/MathView/mathview.js @@ -212,7 +212,10 @@ this.button.addEventListener('show.bs.popover', () => { this.regenPreview(); - MathJax.startup.promise = MathJax.startup.promise.then(() => MathJax.typesetPromise(['.popover'])); + }); + + this.button.addEventListener('inserted.bs.popover', () => { + MathJax.typesetPromise?.([this.popover.tip]); }); // Refresh math in the popover when there is a keyup in the input. @@ -283,7 +286,7 @@ if (this.renderingMode === 'LATEX') this.mviewer.textContent = `\\(${text}\\)`; else this.mviewer.textContent = `\`${text}\``; - MathJax.startup.promise = MathJax.startup.promise.then(() => MathJax.typesetPromise([this.mviewer])); + MathJax.typesetPromise?.([this.mviewer]); } // Create a category from the locale js. Each category is implemented using bootstraps tab feature. The @@ -410,7 +413,12 @@ if (node.dataset.mvInitialized === 'true') continue; if (node.classList.contains('codeshard')) new MathViewer(node); - else node.querySelectorAll('.codeshard').forEach((input) => new MathViewer(input)); + else { + for (const input of node.querySelectorAll('.codeshard')) { + if (input.dataset.mvInitialized === 'true') continue; + new MathViewer(input); + } + } if (node.classList.contains('latexentryfield')) new MathViewer(node, { renderingMode: 'LATEX', @@ -418,14 +426,14 @@ includeDelimiters: true }); else - node.querySelectorAll('.latexentryfield').forEach( - (input) => - new MathViewer(input, { - renderingMode: 'LATEX', - decoratedTextBoxAsInput: false, - includeDelimiters: true - }) - ); + for (const input of node.querySelectorAll('.latexentryfield')) { + if (input.dataset.mvInitialized === 'true') continue; + new MathViewer(input, { + renderingMode: 'LATEX', + decoratedTextBoxAsInput: false, + includeDelimiters: true + }); + } } } } diff --git a/htdocs/js/PODViewer/podviewer.css b/htdocs/js/PODViewer/podviewer.css new file mode 100644 index 0000000000..e4f17811d2 --- /dev/null +++ b/htdocs/js/PODViewer/podviewer.css @@ -0,0 +1,66 @@ +.main-index-header, +.pod-header { + height: 65px; + top: 0; + left: 0; + right: 0; + z-index: 2; +} + +#sidebar { + --bs-offcanvas-width: 300px; + overflow-y: auto; +} + +#sidebar ul.nav ul.nav li { + border-left: 1px solid #e1e4e8; + padding-left: 10px; +} + +#sidebar ul.nav ul.nav li:hover { + border-left: 6px solid #e1e4e8; + padding-left: 5px; +} + +.main-index-container, +.pod-page-container { + margin-top: 65px; +} + +@media only screen and (min-width: 768px) { + #sidebar { + height: calc(100vh - 65px); + width: 300px; + } + + .pod-page-container { + margin-left: 300px; + } +} + +#_podtop_ pre { + border: 1px solid #ccc; + border-radius: 5px; + background: #f6f6f6; + padding: 0.75rem; +} + +#_podtop_, +#_podtop_ *[id] { + scroll-margin-top: calc(65px + 1rem); +} + +@media only screen and (max-width: 768px) { + .pod-header { + height: 100px; + } + + .pod-page-container { + margin-top: 100px; + } + + #_podtop_, + #_podtop_ *[id] { + scroll-margin-top: calc(100px + 1rem); + } +} diff --git a/htdocs/js/PODViewer/podviewer.js b/htdocs/js/PODViewer/podviewer.js new file mode 100644 index 0000000000..795093205a --- /dev/null +++ b/htdocs/js/PODViewer/podviewer.js @@ -0,0 +1,8 @@ +(() => { + const offcanvas = bootstrap.Offcanvas.getOrCreateInstance(document.getElementById('sidebar')); + for (const link of document.querySelectorAll('#sidebar .nav-link')) { + // The timeout is to workaround an issue in Chrome. If the offcanvas hides before the window scrolls to the + // fragment in the page, scrolling stops before it gets there. + link.addEventListener('click', () => setTimeout(() => offcanvas.hide(), 500)); + } +})(); diff --git a/htdocs/js/Plots/plots.js b/htdocs/js/Plots/plots.js new file mode 100644 index 0000000000..8dbe389ae7 --- /dev/null +++ b/htdocs/js/Plots/plots.js @@ -0,0 +1,609 @@ +/* global JXG */ + +'use strict'; + +const PGplots = { + async plot(boardContainerId, plotContents, options) { + const drawBoard = (id) => { + const boundingBox = options.board?.boundingBox ?? [-5, 5, 5, -5]; + + // Disable highlighting for all elements. + JXG.Options.elements.highlight = false; + + // Adjust layers to match standard TikZ layers. The "axis" is bumped up a layer so it is above "axis + // ticks". The rest are on layer 3 by default, so they are moved up to main layer. The remaining layer + // settings should be okay for now. + JXG.Options.layer.axis = 3; + JXG.Options.layer.polygon = 5; + JXG.Options.layer.sector = 5; + JXG.Options.layer.angle = 5; + JXG.Options.layer.integral = 5; + + const board = JXG.JSXGraph.initBoard( + id, + JXG.merge( + { + title: options.board?.title ?? 'Graph', + boundingBox, + showCopyright: false, + axis: false, + drag: { enabled: false }, + showNavigation: options.board?.showNavigation ?? false, + pan: { enabled: options.board?.showNavigation ?? false }, + zoom: { enabled: options.board?.showNavigation ?? false } + }, + options.board?.overrideOptions ?? {} + ) + ); + + // The board now has its own clone of the options with the custom settings above which will apply for + // anything created on the board. So reset the JSXGraph defaults so that other JSXGraph images on the page + // don't get these settings. + JXG.Options.elements.highlight = true; + JXG.Options.layer.axis = 2; + JXG.Options.layer.polygon = 3; + JXG.Options.layer.sector = 3; + JXG.Options.layer.angle = 3; + JXG.Options.layer.integral = 3; + + const descriptionSpan = document.createElement('span'); + descriptionSpan.id = `${id}_description`; + descriptionSpan.classList.add('visually-hidden'); + descriptionSpan.textContent = options.ariaDescription ?? 'Generated graph'; + board.containerObj.after(descriptionSpan); + board.containerObj.setAttribute('aria-describedby', descriptionSpan.id); + + // This object is passed to the plotContents method as its second argument and exposes these methods (and + // potentially other things in the future) to the code that is called in that method. So the JavaScript code + // generated in JSXGraph.pm can use these methods. + const plot = { + // Convert a decimal number into a fraction or mixed number. This is basically the JXG.toFraction method + // except that the "mixed" parameter is added, and it returns an improper fraction if mixed is false. + toFraction(x, useTeX, mixed, order) { + const arr = JXG.Math.decToFraction(x, order); + + if (arr[1] === 0 && arr[2] === 0) { + return '0'; + } else { + let str = ''; + // Sign + if (arr[0] < 0) str += '-'; + if (arr[2] === 0) { + // Integer + str += arr[1]; + } else if (!(arr[2] === 1 && arr[3] === 1)) { + // Proper fraction + if (mixed) { + if (arr[1] !== 0) str += arr[1] + ' '; + if (useTeX) str += `\\frac{${arr[2]}}{${arr[3]}}`; + else str += `${arr[2]}/${arr[3]}`; + } else { + if (useTeX) str += `\\frac{${arr[3] * arr[1] + arr[2]}}{${arr[3]}}`; + else str += `${arr[3] * arr[1] + arr[2]}/${arr[3]}`; + } + } + return str; + } + }, + + // Override the default axis generateLabelText method to show custom tick labels if they are set, and so + // that 0 is displayed using MathJax if the axis is configured to show tick labels using MathJax. + generateLabelText(tick, zero, value) { + for (const [axis, coord] of [ + ['xAxis', 1], + ['yAxis', 2] + ]) { + if ( + this === plot[axis]?.defaultTicks && + typeof options[axis]?.ticks?.labels === 'object' && + tick.usrCoords[coord] in options[axis].ticks.labels + ) { + return options[axis].ticks.labels[tick.usrCoords[coord]]; + } + } + if (JXG.exists(value)) return this.formatLabelText(value); + const distance = this.getDistanceFromZero(zero, tick); + return this.formatLabelText(Math.abs(distance) < JXG.Math.eps ? 0 : distance / this.visProp.scale); + }, + + trimTrailingZeros(value) { + if (value.indexOf('.') > -1 && value.endsWith('0')) { + value = value.replace(/0+$/, ''); + // Remove the decimal if it is now at the end. + value = value.replace(/\.$/, ''); + } + return value; + }, + + // Override the formatLabelText method for the axes ticks so that + // better number formats can be used for tick labels. + formatLabelText(value) { + let labelText; + + if (JXG.isNumber(value)) { + if (this.visProp.label.format === 'fraction' || this.visProp.label.format === 'mixed') { + labelText = plot.toFraction( + value, + this.visProp.label.usemathjax, + this.visProp.label.format === 'mixed' + ); + } else if (this.visProp.label.format === 'scinot') { + const [mantissa, exponent] = value.toExponential(this.visProp.digits).toString().split('e'); + labelText = this.visProp.label.usemathjax + ? `${plot.trimTrailingZeros(mantissa)}\\cdot 10^{${exponent}}` + : `${plot.trimTrailingZeros(mantissa)} x 10^${exponent}`; + } else { + labelText = plot.trimTrailingZeros(value.toFixed(this.visProp.digits).toString()); + } + } else { + labelText = value.toString(); + } + + if (this.visProp.scalesymbol.length > 0) { + if (labelText === '1') labelText = this.visProp.scalesymbol; + else if (labelText === '-1') labelText = `-${this.visProp.scalesymbol}`; + else if (labelText !== '0') labelText = labelText + this.visProp.scalesymbol; + } + + return this.visProp.label.usemathjax ? `\\(${labelText}\\)` : labelText; + }, + + createLabel(x, y, text, options) { + const anchor = options.angleAnchor; + delete options.angleAnchor; + const rotate = options.rotate; + delete options.rotate; + + const textElement = board.create('text', [x, y, text], options); + + if (typeof anchor !== 'undefined') { + const cosA = Math.cos((anchor * Math.PI) / 180); + const sinA = Math.sin((anchor * Math.PI) / 180); + + const transform = board.create( + 'transform', + [ + () => { + const [w, h] = textElement.getSize(); + return ( + (w * Math.abs(sinA) > h * Math.abs(cosA) + ? (-h / 2 / Math.abs(sinA)) * cosA + : ((cosA < 0 ? 1 : -1) * w) / 2) / board.unitX + ); + }, + () => { + const [w, h] = textElement.getSize(); + return ( + (w * Math.abs(sinA) > h * Math.abs(cosA) + ? ((sinA < 0 ? 1 : -1) * h) / 2 + : (-w / 2 / Math.abs(cosA)) * sinA) / board.unitY + ); + } + ], + { type: 'translate' } + ); + transform.bindTo(textElement); + } + if (rotate) textElement.addRotation(rotate); + + return textElement; + } + }; + + board.suspendUpdate(); + + // This axis provides the vertical grid lines. + if (options.grid?.x) { + plot.xGrid = board.create( + 'axis', + [ + [options.xAxis?.min ?? -5, options.xAxis?.position ?? 0], + [options.xAxis?.max ?? 5, options.xAxis?.position ?? 0] + ], + JXG.merge( + { + anchor: + options.xAxis?.location === 'top' + ? 'left' + : options.xAxis?.location === 'bottom' || options.xAxis?.location === 'box' + ? 'right' + : 'right left', + position: + options.xAxis?.location === 'middle' + ? options.board?.showNavigation + ? 'sticky' + : 'static' + : 'fixed', + firstArrow: false, + lastArrow: false, + straightFirst: options.board?.showNavigation ? true : false, + straightLast: options.board?.showNavigation ? true : false, + highlight: false, + strokeOpacity: 0, + ticks: { + drawLabels: false, + drawZero: true, + majorHeight: -1, + minorHeight: -1, + strokeColor: options.grid.color ?? '#808080', + strokeOpacity: options.grid.opacity ?? 0.2, + insertTicks: false, + ticksDistance: options.xAxis.ticks?.positions ?? options.xAxis.ticks?.distance ?? 2, + scale: options.xAxis?.ticks?.scale ?? 1, + minorTicks: options.grid.x.minorGrids ? (options.xAxis?.ticks?.minorTicks ?? 3) : 0, + ignoreInfiniteTickEndings: false, + majorTickEndings: [ + !options.board?.showNavigation && boundingBox[1] > (options.yAxis?.max ?? 5) + ? 0 + : 1, + !options.board?.showNavigation && boundingBox[3] < (options.yAxis?.min ?? -5) + ? 0 + : 1 + ], + tickEndings: [ + !options.board?.showNavigation && boundingBox[1] > (options.yAxis?.max ?? 5) + ? 0 + : 1, + !options.board?.showNavigation && boundingBox[3] < (options.yAxis?.min ?? -5) + ? 0 + : 1 + ] + }, + withLabel: false + }, + options.grid.x.overrideOptions ?? {} + ) + ); + } + + // This axis provides the horizontal grid lines. + if (options.grid?.y) { + plot.yGrid = board.create( + 'axis', + [ + [options.yAxis?.position ?? 0, options.yAxis?.min ?? -5], + [options.yAxis?.position ?? 0, options.yAxis?.max ?? -5] + ], + JXG.merge( + { + anchor: + options.yAxis?.location === 'right' + ? 'right' + : options.yAxis?.location === 'left' || options.yAxis?.location === 'box' + ? 'left' + : 'right left', + position: + options.yAxis?.location === 'center' + ? options.board?.showNavigation + ? 'sticky' + : 'static' + : 'fixed', + firstArrow: false, + lastArrow: false, + straightFirst: options.board?.showNavigation ? true : false, + straightLast: options.board?.showNavigation ? true : false, + highlight: false, + strokeOpacity: 0, + ticks: { + drawLabels: false, + drawZero: true, + majorHeight: -1, + minorHeight: -1, + strokeColor: options.grid.color ?? '#808080', + strokeOpacity: options.grid.opacity ?? 0.2, + insertTicks: false, + ticksDistance: options.yAxis.ticks?.positions ?? options.yAxis.ticks?.distance ?? 2, + scale: options.yAxis?.ticks?.scale ?? 1, + minorTicks: options.grid.y.minorGrids ? (options.yAxis?.ticks?.minorTicks ?? 3) : 0, + ignoreInfiniteTickEndings: false, + majorTickEndings: [ + !options.board?.showNavigation && boundingBox[0] < (options.xAxis?.min ?? -5) + ? 0 + : 1, + !options.board?.showNavigation && boundingBox[2] > (options.xAxis?.max ?? 5) ? 0 : 1 + ], + tickEndings: [ + !options.board?.showNavigation && boundingBox[0] < (options.xAxis?.min ?? -5) + ? 0 + : 1, + !options.board?.showNavigation && boundingBox[2] > (options.xAxis?.max ?? 5) ? 0 : 1 + ] + }, + withLabel: 0 + }, + options.grid.y.overrideOptions ?? {} + ) + ); + } + + if (options.xAxis?.visible) { + const xAxis = (plot.xAxis = board.create( + 'axis', + [ + [options.xAxis.min ?? -5, options.xAxis.position ?? 0], + [options.xAxis.max ?? 5, options.xAxis.position ?? 0] + ], + JXG.merge( + { + name: options.xAxis.name ?? '\\(x\\)', + anchor: + options.xAxis?.location === 'top' + ? 'left' + : options.xAxis?.location === 'bottom' || options.xAxis?.location === 'box' + ? 'right' + : 'right left', + position: + options.xAxis.location === 'middle' + ? options.board?.showNavigation + ? 'sticky' + : 'static' + : 'fixed', + firstArrow: options.xAxis.arrowsBoth ? { size: 7 } : false, + lastArrow: { size: 7 }, + highlight: false, + straightFirst: options.board?.showNavigation ? true : false, + straightLast: options.board?.showNavigation ? true : false, + withLabel: options.xAxis.location === 'middle' ? true : false, + label: { + anchorX: 'right', + anchorY: 'middle', + highlight: false, + offset: [-5, -3], + position: '100% left', + useMathJax: true + }, + ticks: { + drawLabels: options.xAxis.ticks?.labels && options.xAxis.ticks?.show ? true : false, + drawZero: + options.board?.showNavigation || + !options.yAxis?.visible || + (options.yAxis.location === 'center' && (options.yAxis.position ?? 0) != 0) || + ((options.yAxis.location === 'left' || options.yAxis.location === 'box') && + (options.xAxis.min ?? -5) != 0) || + (options.yAxis.location === 'right' && (options.xAxis.max ?? 5) != 0) + ? true + : false, + insertTicks: false, + ticksDistance: options.xAxis.ticks?.positions ?? options.xAxis.ticks?.distance ?? 2, + scale: options.xAxis.ticks?.scale ?? 1, + scaleSymbol: options.xAxis.ticks?.scaleSymbol ?? '', + minorTicks: options.xAxis.ticks?.minorTicks ?? 3, + majorHeight: options.xAxis.ticks?.show ? 8 : 0, + minorHeight: options.xAxis.ticks?.show ? 5 : 0, + strokeWidth: 1.5, + majorTickEndings: [1, options.xAxis.location === 'box' ? 0 : 1], + tickEndings: [1, options.xAxis.location === 'box' ? 0 : 1], + digits: options.xAxis.ticks?.labelDigits ?? 2, + label: { + anchorX: 'middle', + anchorY: options.xAxis.location === 'top' ? 'bottom' : 'top', + offset: options.xAxis.location === 'top' ? [0, 4] : [0, -4], + highlight: 0, + ...(options.mathJaxTickLabels ? { useMathJax: true, display: 'html' } : {}), + format: options.xAxis.ticks?.labelFormat ?? 'decimal' + } + } + }, + options.xAxis.overrideOptions ?? {} + ) + )); + xAxis.defaultTicks.generateLabelText = plot.generateLabelText; + xAxis.defaultTicks.formatLabelText = plot.formatLabelText; + + if (options.xAxis.location !== 'middle' && options.xAxis.name !== '') { + plot.xLabel = board.create( + 'text', + [ + () => (xAxis.point1.X() + xAxis.point2.X()) / 2, + () => + options.xAxis.location === 'top' + ? board.getBoundingBox()[1] - 2 / board.unitY + : board.getBoundingBox()[3] + 2 / board.unitY, + options.xAxis.name ?? '\\(x\\)' + ], + { + anchorX: 'middle', + anchorY: options.xAxis.location === 'top' ? 'top' : 'bottom', + highlight: false, + color: 'black', + fixed: true, + useMathJax: true, + cssStyle: 'line-height: 1;' + } + ); + } + } + + if (options.yAxis?.visible) { + const yAxis = (plot.yAxis = board.create( + 'axis', + [ + [options.yAxis.position ?? 0, options.yAxis.min ?? -5], + [options.yAxis.position ?? 0, options.yAxis.max ?? -5] + ], + JXG.merge( + { + name: options.yAxis.name ?? '\\(y\\)', + anchor: + options.yAxis?.location === 'right' + ? 'right' + : options.yAxis?.location === 'left' || options.yAxis?.location === 'box' + ? 'left' + : 'right left', + position: + options.yAxis.location === 'center' + ? options.board?.showNavigation + ? 'sticky' + : 'static' + : 'fixed', + firstArrow: options.yAxis.arrowsBoth ? { size: 7 } : false, + lastArrow: { size: 7 }, + highlight: false, + straightFirst: options.board?.showNavigation ? true : false, + straightLast: options.board?.showNavigation ? true : false, + withLabel: options.yAxis.location === 'center' ? true : false, + label: { + anchorX: 'middle', + anchorY: 'top', + highlight: false, + distance: 1, + offset: [5, 1], + position: '100% right', + useMathJax: true + }, + ticks: { + drawLabels: options.yAxis.ticks?.labels && options.yAxis.ticks?.show ? true : false, + drawZero: + options.board?.showNavigation || + !options.xAxis?.visible || + (options.xAxis.location === 'middle' && (options.xAxis.position ?? 0) != 0) || + ((options.xAxis.location === 'bottom' || options.xAxis.location === 'box') && + (options.yAxis.min ?? -5) != 0) || + (options.xAxis.location === 'top' && (options.yAxis.max ?? 5) != 0) + ? true + : false, + insertTicks: false, + ticksDistance: options.yAxis.ticks?.positions ?? options.yAxis.ticks?.distance ?? 2, + scale: options.yAxis.ticks?.scale ?? 1, + scaleSymbol: options.yAxis.ticks?.scaleSymbol ?? '', + minorTicks: options.yAxis.ticks?.minorTicks ?? 3, + majorHeight: options.yAxis.ticks?.show ? 8 : 0, + minorHeight: options.yAxis.ticks?.show ? 5 : 0, + strokeWidth: 1.5, + majorTickEndings: [options.yAxis.location === 'box' ? 0 : 1, 1], + tickEndings: [options.yAxis.location === 'box' ? 0 : 1, 1], + digits: options.yAxis.ticks?.labelDigits ?? 2, + label: { + anchorX: options.yAxis.location === 'right' ? 'left' : 'right', + anchorY: 'middle', + offset: options.yAxis.location === 'right' ? [6, 0] : [-6, 0], + highlight: false, + ...(options.mathJaxTickLabels ? { useMathJax: true, display: 'html' } : {}), + format: options.yAxis.ticks?.labelFormat ?? 'decimal' + } + } + }, + options.yAxis.overrideOptions ?? {} + ) + )); + yAxis.defaultTicks.generateLabelText = plot.generateLabelText; + yAxis.defaultTicks.formatLabelText = plot.formatLabelText; + + if (options.yAxis.location !== 'center' && options.yAxis.name !== '') { + plot.yLabel = board.create('text', [0, 0, options.yAxis.name ?? '\\(y\\)'], { + anchorX: 'middle', + anchorY: options.yAxis.location === 'right' ? 'bottom' : 'top', + rotate: 90, + highlight: 0, + color: 'black', + fixed: 1, + useMathJax: 1, + cssStyle: 'line-height: 1;' + }); + const transform = board.create( + 'transform', + [ + () => + options.yAxis.location === 'right' + ? board.getBoundingBox()[2] - 2 / board.unitX + : board.getBoundingBox()[0] + 2 / board.unitX, + () => (yAxis.point1.Y() + yAxis.point2.Y()) / 2 + ], + { type: 'translate' } + ); + transform.bindTo(plot.yLabel); + } + } + + plotContents(board, plot); + + board.unsuspendUpdate(); + + plot.updateBoundingBox = () => { + if (options.board?.showNavigation || (!plot.xAxis && !plot.yAxis)) return; + + const adjustLeft = options.yAxis?.visible && boundingBox[0] < (options.xAxis?.min ?? -5); + const adjustRight = options.yAxis?.visible && boundingBox[2] > (options.xAxis?.max ?? 5); + const adjustBottom = options.xAxis?.visible && boundingBox[3] < (options.yAxis?.min ?? 5); + const adjustTop = options.xAxis?.visible && boundingBox[1] > (options.yAxis?.max ?? -5); + if (!adjustLeft && !adjustRight && !adjustTop && !adjustBottom) return; + + let width = 0; + if (plot.yAxis) { + for (const label of plot.yAxis.defaultTicks.labels) { + const rect = label.rendNode.getBoundingClientRect(); + if (rect.width > width) width = rect.width; + } + if (plot.yLabel) width += plot.yLabel.rendNode.getBoundingClientRect().width + 4; + width += 12; + } + + let height = 0; + if (plot.xAxis) { + for (const label of plot.xAxis.defaultTicks.labels) { + const rect = label.rendNode.getBoundingClientRect(); + if (rect.height > height) height = rect.height; + } + if (plot.xLabel) height += plot.xLabel.rendNode.getBoundingClientRect().height + 4; + height += 8; + } + + const currentBoundingBox = board.getBoundingBox(); + board.setBoundingBox([ + adjustLeft ? options.xAxis.min - width / board.unitX : currentBoundingBox[0], + adjustTop ? options.yAxis.max + height / board.unitY : currentBoundingBox[1], + adjustRight ? options.xAxis.max + width / board.unitX : currentBoundingBox[2], + adjustBottom ? options.yAxis.min - height / board.unitY : currentBoundingBox[3] + ]); + + if (options.yAxis.location !== 'center' && (adjustLeft || adjustRight)) { + const anchorDist = adjustLeft + ? (options.xAxis?.min ?? -5) - board.getBoundingBox()[0] + : board.getBoundingBox()[2] - (options.xAxis?.max ?? -5); + plot.yAxis?.setAttribute({ anchorDist }); + plot.yGrid?.setAttribute({ anchorDist }); + } + if (options.xAxis.location !== 'center' && (adjustBottom || adjustTop)) { + const anchorDist = adjustBottom + ? (options.yAxis?.min ?? -5) - board.getBoundingBox()[3] + : board.getBoundingBox()[1] - (options.yAxis?.max ?? -5); + plot.xAxis?.setAttribute({ anchorDist }); + plot.xGrid?.setAttribute({ anchorDist }); + } + }; + + if (id.startsWith('magnified-')) { + board.containerObj.addEventListener('resized.imageview', () => { + board.resizeContainer(board.containerObj.clientWidth, board.containerObj.clientHeight, true); + setTimeout(plot.updateBoundingBox); + }); + board.containerObj.addEventListener('hidden.imageview', () => JXG.JSXGraph.freeBoard(board)); + } else { + setTimeout(plot.updateBoundingBox); + } + + return board; + }; + + const container = document.getElementById(boardContainerId); + if (!container) return; + + const drawPromise = (id) => + new Promise((resolve) => { + if (container.offsetWidth === 0) { + setTimeout(async () => resolve(await drawPromise(id)), 100); + return; + } + resolve(drawBoard(id)); + }); + + await drawPromise(boardContainerId); + + container.addEventListener('shown.imageview', async () => { + document + .getElementById(`magnified-${boardContainerId}`) + ?.classList.add(...Array.from(container.classList).filter((c) => c !== 'image-view-elt')); + await drawPromise(`magnified-${boardContainerId}`); + }); + } +}; diff --git a/htdocs/js/Plots/plots.scss b/htdocs/js/Plots/plots.scss index 19c3586878..116a1b3a76 100644 --- a/htdocs/js/Plots/plots.scss +++ b/htdocs/js/Plots/plots.scss @@ -1,4 +1,8 @@ .plots-jsxgraph { display: inline-block; - border-radius: 0px; + vertical-align: middle; + + &:not(.plots-jsxgraph-rounded) { + border-radius: 0px; + } } diff --git a/htdocs/js/Problem/problem.scss b/htdocs/js/Problem/problem.scss index 61eb500437..991d0665c8 100644 --- a/htdocs/js/Problem/problem.scss +++ b/htdocs/js/Problem/problem.scss @@ -152,6 +152,10 @@ &.partially-correct::before { content: url("data:image/svg+xml,"); } + + &.unknown::before { + content: url("data:image/svg+xml,"); + } } &.with-message::before { @@ -162,11 +166,93 @@ left: 100%; border: 1px solid black; border-radius: 50%; - background-color: var(--bs-warning); + background-color: #ffc107; padding: 0.25rem; + animation: flash-in 0.75s; + + @keyframes flash-in { + 0% { + filter: brightness(2); + padding: 0.25rem; + } + + 50% { + filter: brightness(2); + padding: 0.5rem; + } + } } } + /* Feedback button colors. */ + .btn-correct { + --bs-btn-color: #fff; + --bs-btn-bg: #198754; + --bs-btn-border-color: #198754; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #157347; + --bs-btn-hover-border-color: #146c43; + --bs-btn-focus-shadow-rgb: 60, 153, 110; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #146c43; + --bs-btn-active-border-color: #13653f; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #198754; + --bs-btn-disabled-border-color: #198754; + } + + .btn-incorrect { + --bs-btn-color: #fff; + --bs-btn-bg: #dc3545; + --bs-btn-border-color: #dc3545; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #bb2d3b; + --bs-btn-hover-border-color: #b02a37; + --bs-btn-focus-shadow-rgb: 225, 83, 97; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #b02a37; + --bs-btn-active-border-color: #a52834; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #dc3545; + --bs-btn-disabled-border-color: #dc3545; + } + + .btn-partially-correct { + --bs-btn-color: #000; + --bs-btn-bg: #ffc107; + --bs-btn-border-color: #ffc107; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #ffca2c; + --bs-btn-hover-border-color: #ffc720; + --bs-btn-focus-shadow-rgb: 217, 164, 6; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #ffcd39; + --bs-btn-active-border-color: #ffc720; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #000; + --bs-btn-disabled-bg: #ffc107; + --bs-btn-disabled-border-color: #ffc107; + } + + .btn-preview { + --bs-btn-color: #fff; + --bs-btn-bg: #1a67ea; + --bs-btn-border-color: #1a67ea; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #1658c7; + --bs-btn-hover-border-color: #1552bb; + --bs-btn-focus-shadow-rgb: 60, 126, 237; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #1552bb; + --bs-btn-active-border-color: #144db0; + --bs-btn-active-shadow: inset 0 3px 5px #00000020; + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #1a67ea; + --bs-btn-disabled-border-color: #1a67ea; + } + .radio-buttons-container, .checkboxes-container { max-width: calc(100% - 1rem - 25px); @@ -242,7 +328,7 @@ .popover-header { text-align: center; cursor: pointer; - --bs-popover-header-bg: var(--bs-info); + --bs-popover-header-bg: #1a67ea; --bs-popover-header-color: white; .btn-close { @@ -254,21 +340,22 @@ &.correct { .popover-header { - --bs-popover-header-bg: var(--bs-success); + --bs-popover-header-bg: #198754; --bs-popover-header-color: white; } } &.incorrect { .popover-header { - --bs-popover-header-bg: var(--bs-danger); + --bs-popover-header-bg: #dc3545; --bs-popover-header-color: white; } } - &.partially-correct { + &.partially-correct, + &.unknown { .popover-header { - --bs-popover-header-bg: var(--bs-warning); + --bs-popover-header-bg: #ffc107; --bs-popover-header-color: black; } } diff --git a/htdocs/js/SampleProblemViewer/documentation-search.js b/htdocs/js/SampleProblemViewer/documentation-search.js new file mode 100644 index 0000000000..e43c878ef7 --- /dev/null +++ b/htdocs/js/SampleProblemViewer/documentation-search.js @@ -0,0 +1,78 @@ +(async () => { + const searchBox = document.getElementById('search-box'); + const resultList = document.getElementById('result-list'); + if (!resultList || !searchBox) return; + + const rootURL = window.pgDocConfig?.rootURL ?? '.'; + const htmlSuffixMutation = window.pgDocConfig?.htmlSuffixMutation ?? [/\.p[gl]$/, '.html']; + const searchDataURL = window.pgDocConfig?.searchDataURL ?? 'sample-problem-search-data.json'; + + let searchData; + try { + const result = await fetch(searchDataURL); + searchData = await result.json(); + } catch (e) { + console.log(e); + return; + } + + const miniSearch = new MiniSearch({ + fields: ['filename', 'name', 'description', 'terms', 'macros', 'subjects'], + storeFields: ['type', 'filename', 'dir', 'description'] + }); + miniSearch.addAll(searchData); + + const searchMacrosCheck = document.getElementById('search-macros'); + const searchSampleProblemsCheck = document.getElementById('search-sample-problems'); + + document.getElementById('clear-search-button')?.addEventListener('click', () => { + searchBox.value = ''; + while (resultList.firstChild) resultList.firstChild.remove(); + }); + + const searchDocumentation = () => { + const searchMacros = searchMacrosCheck?.checked; + const searchSampleProblems = searchSampleProblemsCheck?.checked; + + while (resultList.firstChild) resultList.firstChild.remove(); + + if (!searchBox.value) return; + + for (const result of miniSearch.search(searchBox.value, { prefix: true })) { + if ( + (searchSampleProblems && result.type === 'sample problem') || + (searchMacros && result.type === 'macro') + ) { + const link = document.createElement('a'); + link.classList.add('list-group-item', 'list-group-item-action'); + link.href = `${rootURL}/${ + result.type === 'sample problem' ? 'sampleproblems' : result.type === 'macro' ? 'pod' : '' + }/${result.dir}/${result.filename.replace(...htmlSuffixMutation)}`; + + const linkText = document.createElement('span'); + linkText.classList.add('h4'); + linkText.textContent = `${result.filename} (${result.type})`; + link.append(linkText); + + if (result.description) { + const summary = document.createElement('div'); + summary.textContent = result.description; + link.append(summary); + } + + resultList.append(link); + } + } + + if (resultList.children.length == 0) { + const item = document.createElement('div'); + item.classList.add('alert', 'alert-info'); + item.innerHTML = 'No results found'; + resultList.append(item); + } + }; + + searchBox.addEventListener('keyup', searchDocumentation); + searchMacrosCheck?.addEventListener('change', searchDocumentation); + searchSampleProblemsCheck?.addEventListener('change', searchDocumentation); +})(); diff --git a/htdocs/js/Scaffold/scaffold.scss b/htdocs/js/Scaffold/scaffold.scss index a4528e2323..5393d530b5 100644 --- a/htdocs/js/Scaffold/scaffold.scss +++ b/htdocs/js/Scaffold/scaffold.scss @@ -15,6 +15,13 @@ min-width: 1.25rem; font-weight: bold; } + + // Force the accordion button to a dark color so that it has sufficient contrast even if the page is in dark + // mode. Bootstrap wants to use the dark mode color for this if the page is in dark mode. + &::after { + --bs-accordion-btn-icon: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='%23212529' stroke-linecap='round' stroke-linejoin='round' viewBox='0 0 16 16'%3E%3Cpath d='m2 5 6 6 6-6'/%3E%3C/svg%3E"); + --bs-accordion-btn-active-icon: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='%23001436' stroke-linecap='round' stroke-linejoin='round' viewBox='0 0 16 16'%3E%3Cpath d='m2 5 6 6 6-6'/%3E%3C/svg%3E"); + } } &.cannotopen > button.accordion-button::after { diff --git a/htdocs/package-lock.json b/htdocs/package-lock.json index fd2291b59e..e861d6ab99 100644 --- a/htdocs/package-lock.json +++ b/htdocs/package-lock.json @@ -1,43 +1,47 @@ { "name": "pg.javascript_package_manager", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pg.javascript_package_manager", "license": "GPL-2.0+", "dependencies": { - "@openwebwork/mathquill": "^0.11.1", - "jsxgraph": "^1.10.1", + "@openwebwork/mathquill": "^0.11.5", + "jsxgraph": "^1.12.2", "jszip": "^3.10.1", "jszip-utils": "^0.1.0", - "plotly.js-dist-min": "^2.32.0", - "sortablejs": "^1.15.2" + "plotly.js-dist-min": "^3.5.1", + "sortablejs": "^1.15.7" }, "devDependencies": { - "autoprefixer": "^10.4.19", - "chokidar": "^3.6.0", - "cssnano": "^6.1.2", - "postcss": "^8.5.10", - "prettier": "^3.2.5", - "rtlcss": "^4.1.1", - "sass": "^1.75.0", - "terser": "^5.30.4", - "yargs": "^17.7.2" + "autoprefixer": "^10.5.0", + "chokidar": "^5.0.0", + "cssnano": "^8.0.1", + "postcss": "^8.5.15", + "prettier": "^3.8.3", + "rtlcss": "^4.3.0", + "sass": "^1.100.0", + "terser": "^5.48.0", + "yargs": "^18.0.0" } }, + "node_modules/@colordx/core": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/@colordx/core/-/core-5.4.3.tgz", + "integrity": "sha512-kIxYSfA5T8HXjav55UaaH/o/cKivF6jCCGIb8eqtcsfI46wsvlSiT8jMDyrl779qLec3c2c2oHBZo4oAhvbjrQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -45,56 +49,394 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@openwebwork/mathquill": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@openwebwork/mathquill/-/mathquill-0.11.1.tgz", - "integrity": "sha512-NLmu+AQ8i4bXX6Zuv5YMOkxhLsZisIW9pB0OvIIajVtAIsUu9ES7aG5Bhq823Ohc9xxA+2S7YrtmRaiDbp+GKA==", - "license": "MPL-2.0" + "version": "0.11.5", + "resolved": "https://registry.npmjs.org/@openwebwork/mathquill/-/mathquill-0.11.5.tgz", + "integrity": "sha512-hX0aj8FayJnwpTYWHTDY/2j4OvYXDoUJIsLtciwCqB7isI7/gEdefWvtQle3UWXD/H7XpBtEZXEoVjc7zmn42Q==", + "license": "MPL-2.0", + "dependencies": { + "bootstrap": "^5.3.8" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -103,46 +445,35 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", "dev": true, "funding": [ { @@ -158,12 +489,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -176,37 +507,49 @@ "postcss": "^8.1.0" } }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "node_modules/baseline-browser-mapping": { + "version": "2.10.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz", + "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==", "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, "engines": { - "node": ">=8" + "node": ">=6.0.0" } }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" + "license": "ISC" + }, + "node_modules/bootstrap": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", + "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "@popperjs/core": "^2.11.8" } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, "funding": [ { @@ -222,11 +565,13 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -239,13 +584,15 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", "dev": true, + "license": "MIT", "dependencies": { "browserslist": "^4.0.0", "caniuse-lite": "^1.0.0", @@ -254,9 +601,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001723", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz", - "integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==", + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", "dev": true, "funding": [ { @@ -271,101 +618,62 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "dev": true, + "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^5.0.0" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 20.19.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" } }, "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", "dev": true, + "license": "ISC", "dependencies": { - "color-name": "~1.1.4" + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=20" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true - }, "node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 10" + "node": ">=16" } }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "node_modules/css-declaration-sorter": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", - "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", - "dev": true, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.0.9" - } + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" }, "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", @@ -378,23 +686,25 @@ } }, "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "dev": true, + "license": "MIT", "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">= 6" }, @@ -407,6 +717,7 @@ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, + "license": "MIT", "bin": { "cssesc": "bin/cssesc" }, @@ -415,79 +726,81 @@ } }, "node_modules/cssnano": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", - "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-8.0.1.tgz", + "integrity": "sha512-oSiOnPQNNYjusTUlYJiE6xvFQG4don3N0QavaoV1BxIsC1zjvxOwikXlR7lG1EVmZNDDaJkHbQx1VRB8kaoMHA==", "dev": true, + "license": "MIT", "dependencies": { - "cssnano-preset-default": "^6.1.2", - "lilconfig": "^3.1.1" + "cssnano-preset-default": "^8.0.1", + "lilconfig": "^3.1.3" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/cssnano" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/cssnano-preset-default": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", - "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", - "dev": true, - "dependencies": { - "browserslist": "^4.23.0", - "css-declaration-sorter": "^7.2.0", - "cssnano-utils": "^4.0.2", - "postcss-calc": "^9.0.1", - "postcss-colormin": "^6.1.0", - "postcss-convert-values": "^6.1.0", - "postcss-discard-comments": "^6.0.2", - "postcss-discard-duplicates": "^6.0.3", - "postcss-discard-empty": "^6.0.3", - "postcss-discard-overridden": "^6.0.2", - "postcss-merge-longhand": "^6.0.5", - "postcss-merge-rules": "^6.1.1", - "postcss-minify-font-values": "^6.1.0", - "postcss-minify-gradients": "^6.0.3", - "postcss-minify-params": "^6.1.0", - "postcss-minify-selectors": "^6.0.4", - "postcss-normalize-charset": "^6.0.2", - "postcss-normalize-display-values": "^6.0.2", - "postcss-normalize-positions": "^6.0.2", - "postcss-normalize-repeat-style": "^6.0.2", - "postcss-normalize-string": "^6.0.2", - "postcss-normalize-timing-functions": "^6.0.2", - "postcss-normalize-unicode": "^6.1.0", - "postcss-normalize-url": "^6.0.2", - "postcss-normalize-whitespace": "^6.0.2", - "postcss-ordered-values": "^6.0.2", - "postcss-reduce-initial": "^6.1.0", - "postcss-reduce-transforms": "^6.0.2", - "postcss-svgo": "^6.0.3", - "postcss-unique-selectors": "^6.0.4" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-8.0.1.tgz", + "integrity": "sha512-OTdKeYMlvQ8KBgyej5ysktnWJoeyo7rGrVnm+bdpIHGvxhbTGPsOkB+7T1EdTuX00dGlQQb2UEbSPB1OpMXULw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.2", + "cssnano-utils": "^6.0.0", + "postcss-calc": "^10.1.1", + "postcss-colormin": "^8.0.0", + "postcss-convert-values": "^8.0.0", + "postcss-discard-comments": "^8.0.0", + "postcss-discard-duplicates": "^8.0.0", + "postcss-discard-empty": "^8.0.0", + "postcss-discard-overridden": "^8.0.0", + "postcss-merge-longhand": "^8.0.0", + "postcss-merge-rules": "^8.0.0", + "postcss-minify-font-values": "^8.0.0", + "postcss-minify-gradients": "^8.0.0", + "postcss-minify-params": "^8.0.0", + "postcss-minify-selectors": "^8.0.1", + "postcss-normalize-charset": "^8.0.0", + "postcss-normalize-display-values": "^8.0.0", + "postcss-normalize-positions": "^8.0.0", + "postcss-normalize-repeat-style": "^8.0.0", + "postcss-normalize-string": "^8.0.0", + "postcss-normalize-timing-functions": "^8.0.0", + "postcss-normalize-unicode": "^8.0.0", + "postcss-normalize-url": "^8.0.0", + "postcss-normalize-whitespace": "^8.0.0", + "postcss-ordered-values": "^8.0.0", + "postcss-reduce-initial": "^8.0.0", + "postcss-reduce-transforms": "^8.0.0", + "postcss-svgo": "^8.0.0", + "postcss-unique-selectors": "^8.0.0" + }, + "engines": { + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/cssnano-utils": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", - "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-6.0.0.tgz", + "integrity": "sha512-ztS9W/+uaDn+bkYmDhs+GdMveHJ3CL8IPNHpRqDUQXv5GJOTQAJjV1XUOInr9esLXSabQV1pLRZlJpyUwEqDyQ==", "dev": true, + "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/csso": { @@ -495,6 +808,7 @@ "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", "dev": true, + "license": "MIT", "dependencies": { "css-tree": "~2.2.0" }, @@ -508,6 +822,7 @@ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", "dev": true, + "license": "MIT", "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" @@ -521,13 +836,26 @@ "version": "2.0.28", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", - "dev": true + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "dev": true, + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -547,13 +875,15 @@ "type": "github", "url": "https://github.com/sponsors/fb55" } - ] + ], + "license": "BSD-2-Clause" }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" }, @@ -565,10 +895,11 @@ } }, "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -579,22 +910,25 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.747", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.747.tgz", - "integrity": "sha512-+FnSWZIAvFHbsNVmUxhEqWiaOiPMcfum1GQzlWCg/wLigVtshOsjXHyEFfmt6cFK6+HkS3QOJBv6/3OPumbBfw==", - "dev": true + "version": "1.5.361", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.361.tgz", + "integrity": "sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==", + "dev": true, + "license": "ISC" }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -603,125 +937,89 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", "dev": true, + "license": "MIT", "engines": { "node": "*" }, "funding": { - "type": "patreon", + "type": "github", "url": "https://github.com/sponsors/rawify" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" }, "node_modules/immutable": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.8.tgz", - "integrity": "sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==", - "dev": true + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", + "dev": true, + "license": "MIT" }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -729,25 +1027,20 @@ "node": ">=0.10.0" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" }, "node_modules/jsxgraph": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/jsxgraph/-/jsxgraph-1.10.1.tgz", - "integrity": "sha512-N7WQmjeiiGKiJPr4iGUHgf8uRazOo9qaGLjX/tLWvrup67FUVD4eEctSLO1HuNcOSthwl16aTc692TKf/vZxNw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/jsxgraph/-/jsxgraph-1.12.2.tgz", + "integrity": "sha512-7kTscexFVBHirsBrxZFQg2hA6Mf/Pa2piojNgxHZ9i/rQfxDAaq7p8oD9/009clQTFQ9fNLUpmDKwV+84zA2Gg==", "license": "(MIT OR LGPL-3.0-or-later)", + "dependencies": { + "jsxgraph": "^1.11.0-beta2" + }, "engines": { "node": ">=0.6.0" } @@ -756,6 +1049,7 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", @@ -766,21 +1060,24 @@ "node_modules/jszip-utils": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/jszip-utils/-/jszip-utils-0.1.0.tgz", - "integrity": "sha512-tBNe0o3HAf8vo0BrOYnLPnXNo5A3KsRMnkBFYjh20Y3GPYGfgyoclEMgvVchx0nnL+mherPi74yLPIusHUQpZg==" + "integrity": "sha512-tBNe0o3HAf8vo0BrOYnLPnXNo5A3KsRMnkBFYjh20Y3GPYGfgyoclEMgvVchx0nnL+mherPi74yLPIusHUQpZg==", + "license": "(MIT OR GPL-3.0)" }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", "dependencies": { "immediate": "~3.0.5" } }, "node_modules/lilconfig": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", - "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" }, @@ -792,24 +1089,27 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, "funding": [ { @@ -817,6 +1117,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -824,28 +1125,22 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", "dev": true, - "engines": { - "node": ">=0.10.0" - } + "license": "MIT", + "optional": true }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "node_modules/node-releases": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", + "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, "node_modules/nth-check": { @@ -853,6 +1148,7 @@ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0" }, @@ -863,35 +1159,40 @@ "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, + "license": "MIT", + "optional": true, "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/plotly.js-dist-min": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-2.32.0.tgz", - "integrity": "sha512-UVznwUQVc7NeFih0tnIbvCpxct+Jxt6yxOGTYJF4vkKIUyujvyiTrH+XazglvcXdybFLERMu/IKt6Lhz3+BqMQ==" + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-3.5.1.tgz", + "integrity": "sha512-N2R4RXKFSRTapdMc/+CDIbvcgFk3HNhs3tR6cKK499tNrSo7C3X7p20Wqi7qpf03s+3oH+DC/hDINYlsE8hxxA==", + "license": "MIT" }, "node_modules/postcss": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", - "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -907,8 +1208,9 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -917,386 +1219,418 @@ } }, "node_modules/postcss-calc": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", - "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz", + "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==", "dev": true, + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.11", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12 || ^20.9 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.2" + "postcss": "^8.4.38" } }, "node_modules/postcss-colormin": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", - "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-8.0.0.tgz", + "integrity": "sha512-KKwMmsSgsmdYXqrjQeqL3tnuIFtctiR1GEMHdjNpDpz/TCRkkkok2mMcreK2zVV3l7POWOmAkR2xYHUpRUK1DA==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "@colordx/core": "^5.4.3", + "browserslist": "^4.28.2", "caniuse-api": "^3.0.0", - "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-convert-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", - "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-8.0.0.tgz", + "integrity": "sha512-Ohtj3rNZWawTRePv5NCHTy8VJSdJ/G/uKuxcxJreOMichuqcT6uEl2TAnopVeJCJ/c13jaSqg7m63yFLM5zBsA==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "browserslist": "^4.28.2", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-discard-comments": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", - "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-8.0.0.tgz", + "integrity": "sha512-zGpvVLj2sbagEp+BTVETvAfkZdGVA6rALNujDK/WTIjdf1/rQOxOG8BBzkI8UQgnw8SkL6xffAfbtGMHFypadw==", "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.1.1" + }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-discard-duplicates": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", - "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-8.0.0.tgz", + "integrity": "sha512-zjRyYmNGI3PTipKBBtCgExlmZXQn49KvKoaiNnR2g+iXxeNk7GY5Js2ULtZXPrCYeqjPagrzKIBNcBocvXCR7g==", "dev": true, + "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-discard-empty": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", - "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-8.0.0.tgz", + "integrity": "sha512-kxPJg6EqahbBvm+l7hpYYCtpsv8dlz7Tv6wJXUXZaeuY0WGS61DxfGdZR4uVB/Cx+yi3iOHQVSqpSHKMFaBg6Q==", "dev": true, + "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-discard-overridden": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", - "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-8.0.0.tgz", + "integrity": "sha512-sW2OWH3l9p0FmBSVr228uztFseqroZxwgD7SGF0Ks0dRPDttSo3P8FK5ZBLtWBH2A5+chpB0J2fB/T8heKHLBw==", "dev": true, + "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-merge-longhand": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", - "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-8.0.0.tgz", + "integrity": "sha512-YDmAmQ8H+ljfomVpSXvr9NA0GP01fraQJqjWBYoMVGg6rOT+PJLwPyeVo2ekn4WB4ZVSH5ddtK3DTRxbz6CFzg==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.1.1" + "stylehacks": "^8.0.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-merge-rules": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", - "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-8.0.0.tgz", + "integrity": "sha512-bgstL5mpi41dDpnYGDUcI3M814NWkCMcIWpwDqEHXkHg3BT7b4XRAfNEuwJncZOVn/67kVKvWzhfv/7xyrp2uQ==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "browserslist": "^4.28.2", "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.2", - "postcss-selector-parser": "^6.0.16" + "cssnano-utils": "^6.0.0", + "postcss-selector-parser": "^7.1.1" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-minify-font-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", - "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-8.0.0.tgz", + "integrity": "sha512-EnOHQEnSt6oH5NrL1DMFAQuwB2IOimFXTCzc9bKfUeH1jREbqIF5MAK4gQJQOC4mPUwJt4sWifAmNZ1qLu6j3Q==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-minify-gradients": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", - "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-8.0.0.tgz", + "integrity": "sha512-43iAnYIGk0ZjNx5X/rkIcHi6dhmu/vEjY0kqfUfxPuJRO+V7jx8uKIdcnL0dpfNoC5J9TSh3EtzLWbq0gpqnWA==", "dev": true, + "license": "MIT", "dependencies": { - "colord": "^2.9.3", - "cssnano-utils": "^4.0.2", + "@colordx/core": "^5.4.3", + "cssnano-utils": "^6.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-minify-params": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", - "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-8.0.0.tgz", + "integrity": "sha512-z7w4QO7G55l4vMUK1Lmx03GW7iyRLgf2V5Dz/7ioSPLnXRjeD+b7m0XfAXUGrbBYYrJ6bXPk+3LoX5u4JfAcSg==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "cssnano-utils": "^4.0.2", + "browserslist": "^4.28.2", + "cssnano-utils": "^6.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-minify-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", - "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-8.0.1.tgz", + "integrity": "sha512-c31D46811kTkQDxV1KTTow79axX6gj/01AY5G7cGZg3s31KvAwP13jEFXGAzQbJ7NvOFV1pRqEia6nrAdHU7qg==", "dev": true, + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.16" + "browserslist": "^4.28.1", + "caniuse-api": "^3.0.0", + "cssesc": "^3.0.0", + "postcss-selector-parser": "^7.1.1" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-normalize-charset": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", - "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-8.0.0.tgz", + "integrity": "sha512-s88FUNDSUD8m0wBYvTQQcubVts6zhXwBU8zCD4vkRKiecd0v8cOjHVIF9r/i+5xzS/WG3f98qq4XsOM0JqvfLA==", "dev": true, + "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-normalize-display-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", - "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-8.0.0.tgz", + "integrity": "sha512-gG2nBxD27fiw6Luinb1QYKdM/Co5GornRJgSD+JTwNH4PGKxImP0qyruDDav49aHUPLY3qrL3qN3LvybO7IzxQ==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-normalize-positions": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", - "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-8.0.0.tgz", + "integrity": "sha512-t/wGqpehS20Ke7kc4QAsWpH+AJjUdMK/V5qV2RhrXkj8hO/fT1t1MJ8NL7sedWYk7ZqC7eISEJQonW5j0tU1MQ==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-normalize-repeat-style": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", - "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-8.0.0.tgz", + "integrity": "sha512-3ebOmGdCYKrBYyGKc1xhj0unEnW7beZpVU7JohVeGl7mTxR+7T6egpaawTWAVsB0pEIhcsbJVOjPKCJSoRO6Zg==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-normalize-string": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", - "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-8.0.0.tgz", + "integrity": "sha512-TvWCGZ/e04Tv31uJvOUtbexkfgUnqmQ3M2P5DkAaVzvOj+BvTkG2QjpA5Y71SL1SPxJcj4M23fNh+RDVCmG8kA==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-normalize-timing-functions": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", - "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-8.0.0.tgz", + "integrity": "sha512-uEfaXst5Xgqxv7geYUuz6vs9mn88K2NPY2RoIzM3BMmSjsdTSeppV9x2qIgrxsisdbSqF6IVhzI2occcte3hTA==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-normalize-unicode": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", - "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-8.0.0.tgz", + "integrity": "sha512-+WYngZaChEeTHZmWhmKtnJ4gTzWdINEaFcgWBnu6WdVu8Ftim8OBTcw768DuCC/3Aax9bZ9WkwrLGHym2Lzf+A==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "browserslist": "^4.28.2", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-normalize-url": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", - "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-8.0.0.tgz", + "integrity": "sha512-4Mz9hZHn/QIB+YtFqTXrDmE2193GYxGb3F8uMfLvMicaEXCCUlDIJ658gFFJbqEGl9FYzwPtRiuNgbwlO9kkBg==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-normalize-whitespace": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", - "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-8.0.0.tgz", + "integrity": "sha512-V1f8tYnwIP5tscOXQFTKK8Y5EJ+R2GMpFJ6FjzwoKoQnhbqQy3IeSrDjJJb8JjVos8ut6Osi80Zybpayv/XjIQ==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-ordered-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", - "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-8.0.0.tgz", + "integrity": "sha512-Dg9+itb6lmD0bxqhQyHCtXAwYRh0wUrx6Mp4/BNXgkLoJmdYMmWi+V+Pypw79Q6iQhxA8KFMHqLBITQJV2gKMA==", "dev": true, + "license": "MIT", "dependencies": { - "cssnano-utils": "^4.0.2", + "cssnano-utils": "^6.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-reduce-initial": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", - "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-8.0.0.tgz", + "integrity": "sha512-DChcE9d528AKrlpCTHjhsAiOsWCk4H9ApHPS1QqRT3praObWTiWyn6W1UddGpc46K9LQnHwUu4YwaPUukGtXVA==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "browserslist": "^4.28.2", "caniuse-api": "^3.0.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-reduce-transforms": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", - "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-8.0.0.tgz", + "integrity": "sha512-cLZT0som7vvumQT9XQCnSKOSnRinNQZd1Hm+J723Ney13E8CIydDhw6JwzsjPtgnYThTqn9Q45906gz6wxaAsw==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-selector-parser": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", - "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -1306,47 +1640,51 @@ } }, "node_modules/postcss-svgo": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", - "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-8.0.0.tgz", + "integrity": "sha512-Q2fMSYEiNE1ioDc/3sxvI24NdgA/MJno2XLNpOxgv8aCcJbym8mZY10/lDY5+AWCIc3Aiqzy2Wcp9/zaIXBZgQ==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", - "svgo": "^3.2.0" + "svgo": "^4.0.1" }, "engines": { - "node": "^14 || ^16 || >= 18" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-unique-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", - "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-8.0.0.tgz", + "integrity": "sha512-iObuolUX+ITJfMU2QQFQdh31JgSjNLPNjVs6YGAqBHvOvAWXMMNget6donQl83aQaeS32i5XeKZURUW/WBxIUw==", "dev": true, + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.16" + "postcss-selector-parser": "^7.1.1" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -1360,12 +1698,14 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1377,31 +1717,25 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/rtlcss": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz", - "integrity": "sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", + "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", "dev": true, + "license": "MIT", "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0", @@ -1418,30 +1752,36 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" }, "node_modules/sass": { - "version": "1.75.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz", - "integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.100.0.tgz", + "integrity": "sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==", "dev": true, + "license": "MIT", "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", + "chokidar": "^5.0.0", + "immutable": "^5.1.5", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { "sass": "sass.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.19.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" } }, "node_modules/sax": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz", - "integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", "dev": true, + "license": "BlueOak-1.0.0", "engines": { "node": ">=11.0.0" } @@ -1449,18 +1789,21 @@ "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" }, "node_modules/sortablejs": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.2.tgz", - "integrity": "sha512-FJF5jgdfvoKn1MAKSdGs33bIqLi3LmsgVTliuX6iITj834F+JRQZN90Z93yql8h0K2t0RwDPBmxwlbZfDcxNZA==" + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.7.tgz", + "integrity": "sha512-Kk8wLQPlS+yi1ZEf48a4+fzHa4yxjC30M/Sr2AnQu+f/MPwvvX9XjZ6OWejiz8crBsLwSq8GHqaxaET7u6ux0A==", + "license": "MIT" }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -1470,6 +1813,7 @@ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -1479,6 +1823,7 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -1488,34 +1833,43 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } }, "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, + "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^6.2.2" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/strip-json-comments": { @@ -1523,6 +1877,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -1531,40 +1886,42 @@ } }, "node_modules/stylehacks": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", - "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-8.0.0.tgz", + "integrity": "sha512-sWyjaJvBqHoVKYPbQ8JRvrGSPaYWtWrJsU+fGVtwKB1GE1rRPu3rC7T6UCuXLoL00Dwb+tsHe2T904r8Vnsx8w==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "postcss-selector-parser": "^6.0.16" + "browserslist": "^4.28.2", + "postcss-selector-parser": "^7.1.1" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^22.11.0 || ^24.11.0 || >=26.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.14" } }, "node_modules/svgo": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.3.tgz", - "integrity": "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz", + "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==", "dev": true, + "license": "MIT", "dependencies": { - "commander": "^7.2.0", + "commander": "^11.1.0", "css-select": "^5.1.0", - "css-tree": "^2.3.1", + "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", - "picocolors": "^1.0.0", + "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": { - "svgo": "bin/svgo" + "svgo": "bin/svgo.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=16" }, "funding": { "type": "opencollective", @@ -1572,13 +1929,14 @@ } }, "node_modules/terser": { - "version": "5.30.4", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.4.tgz", - "integrity": "sha512-xRdd0v64a8mFK9bnsKVdoNP9GQIKUAaJPTaqEQDL4w/J8WaW4sWXXoMZ+6SimPkfT5bElreXf8m9HnmPc3E1BQ==", + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", + "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -1593,24 +1951,13 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } + "license": "MIT" }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -1626,9 +1973,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -1640,20 +1988,22 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -1664,1164 +2014,38 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", "dev": true, + "license": "MIT", "dependencies": { - "cliui": "^8.0.1", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", + "string-width": "^7.2.0", "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "yargs-parser": "^22.0.0" }, "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "dev": true, + "license": "ISC", "engines": { - "node": ">=12" - } - } - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true - }, - "@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "@openwebwork/mathquill": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@openwebwork/mathquill/-/mathquill-0.11.1.tgz", - "integrity": "sha512-NLmu+AQ8i4bXX6Zuv5YMOkxhLsZisIW9pB0OvIIajVtAIsUu9ES7aG5Bhq823Ohc9xxA+2S7YrtmRaiDbp+GKA==" - }, - "acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", - "dev": true, - "requires": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - } - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, - "braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "requires": { - "fill-range": "^7.1.1" - } - }, - "browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "dev": true, - "requires": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "caniuse-lite": { - "version": "1.0.30001723", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz", - "integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==", - "dev": true - }, - "chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true - }, - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true - }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "css-declaration-sorter": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", - "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", - "dev": true, - "requires": {} - }, - "css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "dev": true, - "requires": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - } - }, - "css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "dev": true, - "requires": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - } - }, - "css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true - }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true - }, - "cssnano": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", - "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", - "dev": true, - "requires": { - "cssnano-preset-default": "^6.1.2", - "lilconfig": "^3.1.1" - } - }, - "cssnano-preset-default": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", - "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", - "dev": true, - "requires": { - "browserslist": "^4.23.0", - "css-declaration-sorter": "^7.2.0", - "cssnano-utils": "^4.0.2", - "postcss-calc": "^9.0.1", - "postcss-colormin": "^6.1.0", - "postcss-convert-values": "^6.1.0", - "postcss-discard-comments": "^6.0.2", - "postcss-discard-duplicates": "^6.0.3", - "postcss-discard-empty": "^6.0.3", - "postcss-discard-overridden": "^6.0.2", - "postcss-merge-longhand": "^6.0.5", - "postcss-merge-rules": "^6.1.1", - "postcss-minify-font-values": "^6.1.0", - "postcss-minify-gradients": "^6.0.3", - "postcss-minify-params": "^6.1.0", - "postcss-minify-selectors": "^6.0.4", - "postcss-normalize-charset": "^6.0.2", - "postcss-normalize-display-values": "^6.0.2", - "postcss-normalize-positions": "^6.0.2", - "postcss-normalize-repeat-style": "^6.0.2", - "postcss-normalize-string": "^6.0.2", - "postcss-normalize-timing-functions": "^6.0.2", - "postcss-normalize-unicode": "^6.1.0", - "postcss-normalize-url": "^6.0.2", - "postcss-normalize-whitespace": "^6.0.2", - "postcss-ordered-values": "^6.0.2", - "postcss-reduce-initial": "^6.1.0", - "postcss-reduce-transforms": "^6.0.2", - "postcss-svgo": "^6.0.3", - "postcss-unique-selectors": "^6.0.4" - } - }, - "cssnano-utils": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", - "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", - "dev": true, - "requires": {} - }, - "csso": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", - "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", - "dev": true, - "requires": { - "css-tree": "~2.2.0" - }, - "dependencies": { - "css-tree": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", - "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", - "dev": true, - "requires": { - "mdn-data": "2.0.28", - "source-map-js": "^1.0.1" - } - }, - "mdn-data": { - "version": "2.0.28", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", - "dev": true - } - } - }, - "dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "requires": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" + "node": "^20.19.0 || ^22.12.0 || >=23" } - }, - "domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true - }, - "domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "requires": { - "domelementtype": "^2.3.0" - } - }, - "domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", - "dev": true, - "requires": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - } - }, - "electron-to-chromium": { - "version": "1.4.747", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.747.tgz", - "integrity": "sha512-+FnSWZIAvFHbsNVmUxhEqWiaOiPMcfum1GQzlWCg/wLigVtshOsjXHyEFfmt6cFK6+HkS3QOJBv6/3OPumbBfw==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true - }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" - }, - "immutable": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.8.tgz", - "integrity": "sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==", - "dev": true - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "jsxgraph": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/jsxgraph/-/jsxgraph-1.10.1.tgz", - "integrity": "sha512-N7WQmjeiiGKiJPr4iGUHgf8uRazOo9qaGLjX/tLWvrup67FUVD4eEctSLO1HuNcOSthwl16aTc692TKf/vZxNw==" - }, - "jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "requires": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, - "jszip-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/jszip-utils/-/jszip-utils-0.1.0.tgz", - "integrity": "sha512-tBNe0o3HAf8vo0BrOYnLPnXNo5A3KsRMnkBFYjh20Y3GPYGfgyoclEMgvVchx0nnL+mherPi74yLPIusHUQpZg==" - }, - "lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "requires": { - "immediate": "~3.0.5" - } - }, - "lilconfig": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", - "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", - "dev": true - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "dev": true - }, - "mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true - }, - "nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true - }, - "node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true - }, - "nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "requires": { - "boolbase": "^1.0.0" - } - }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" - }, - "picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true - }, - "picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true - }, - "plotly.js-dist-min": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-2.32.0.tgz", - "integrity": "sha512-UVznwUQVc7NeFih0tnIbvCpxct+Jxt6yxOGTYJF4vkKIUyujvyiTrH+XazglvcXdybFLERMu/IKt6Lhz3+BqMQ==" - }, - "postcss": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", - "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", - "dev": true, - "requires": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - } - }, - "postcss-calc": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", - "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.11", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-colormin": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", - "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", - "dev": true, - "requires": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0", - "colord": "^2.9.3", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-convert-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", - "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", - "dev": true, - "requires": { - "browserslist": "^4.23.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-discard-comments": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", - "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", - "dev": true, - "requires": {} - }, - "postcss-discard-duplicates": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", - "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", - "dev": true, - "requires": {} - }, - "postcss-discard-empty": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", - "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", - "dev": true, - "requires": {} - }, - "postcss-discard-overridden": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", - "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", - "dev": true, - "requires": {} - }, - "postcss-merge-longhand": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", - "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.1.1" - } - }, - "postcss-merge-rules": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", - "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", - "dev": true, - "requires": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.2", - "postcss-selector-parser": "^6.0.16" - } - }, - "postcss-minify-font-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", - "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-minify-gradients": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", - "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", - "dev": true, - "requires": { - "colord": "^2.9.3", - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-minify-params": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", - "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", - "dev": true, - "requires": { - "browserslist": "^4.23.0", - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-minify-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", - "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.16" - } - }, - "postcss-normalize-charset": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", - "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", - "dev": true, - "requires": {} - }, - "postcss-normalize-display-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", - "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-positions": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", - "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-repeat-style": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", - "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-string": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", - "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-timing-functions": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", - "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-unicode": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", - "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", - "dev": true, - "requires": { - "browserslist": "^4.23.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-url": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", - "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-whitespace": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", - "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-ordered-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", - "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", - "dev": true, - "requires": { - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-reduce-initial": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", - "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", - "dev": true, - "requires": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0" - } - }, - "postcss-reduce-transforms": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", - "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-selector-parser": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", - "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - } - }, - "postcss-svgo": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", - "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0", - "svgo": "^3.2.0" - } - }, - "postcss-unique-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", - "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.16" - } - }, - "postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "rtlcss": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz", - "integrity": "sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==", - "dev": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0", - "postcss": "^8.4.21", - "strip-json-comments": "^3.1.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "sass": { - "version": "1.75.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz", - "integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==", - "dev": true, - "requires": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", - "source-map-js": ">=0.6.2 <2.0.0" - } - }, - "sax": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz", - "integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==", - "dev": true - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" - }, - "sortablejs": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.2.tgz", - "integrity": "sha512-FJF5jgdfvoKn1MAKSdGs33bIqLi3LmsgVTliuX6iITj834F+JRQZN90Z93yql8h0K2t0RwDPBmxwlbZfDcxNZA==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "stylehacks": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", - "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", - "dev": true, - "requires": { - "browserslist": "^4.23.0", - "postcss-selector-parser": "^6.0.16" - } - }, - "svgo": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.3.tgz", - "integrity": "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==", - "dev": true, - "requires": { - "commander": "^7.2.0", - "css-select": "^5.1.0", - "css-tree": "^2.3.1", - "css-what": "^6.1.0", - "csso": "^5.0.5", - "picocolors": "^1.0.0", - "sax": "^1.5.0" - } - }, - "terser": { - "version": "5.30.4", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.4.tgz", - "integrity": "sha512-xRdd0v64a8mFK9bnsKVdoNP9GQIKUAaJPTaqEQDL4w/J8WaW4sWXXoMZ+6SimPkfT5bElreXf8m9HnmPc3E1BQ==", - "dev": true, - "requires": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - } - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", - "dev": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true } } } diff --git a/htdocs/package.json b/htdocs/package.json index ed9a70f749..9c14ec221e 100644 --- a/htdocs/package.json +++ b/htdocs/package.json @@ -13,23 +13,23 @@ "prettier-check": "prettier --ignore-path=../.gitignore --check \"**/*.{js,css,scss,html}\" \"../**/*.dist.yml\"" }, "dependencies": { - "@openwebwork/mathquill": "^0.11.1", - "jsxgraph": "^1.10.1", + "@openwebwork/mathquill": "^0.11.5", + "jsxgraph": "^1.12.2", "jszip": "^3.10.1", "jszip-utils": "^0.1.0", - "plotly.js-dist-min": "^2.32.0", - "sortablejs": "^1.15.2" + "plotly.js-dist-min": "^3.5.1", + "sortablejs": "^1.15.7" }, "devDependencies": { - "autoprefixer": "^10.4.19", - "chokidar": "^3.6.0", - "cssnano": "^6.1.2", - "postcss": "^8.5.10", - "prettier": "^3.2.5", - "rtlcss": "^4.1.1", - "sass": "^1.75.0", - "terser": "^5.30.4", - "yargs": "^17.7.2" + "autoprefixer": "^10.5.0", + "chokidar": "^5.0.0", + "cssnano": "^8.0.1", + "postcss": "^8.5.15", + "prettier": "^3.8.3", + "rtlcss": "^4.3.0", + "sass": "^1.100.0", + "terser": "^5.48.0", + "yargs": "^18.0.0" }, "browserslist": [ "last 10 Chrome versions", @@ -41,5 +41,8 @@ "last 8 FirefoxAndroid versions", "last 10 iOS versions", "last 5 Opera versions" - ] + ], + "allowScripts": { + "@parcel/watcher": false + } } diff --git a/lib/AnswerHash.pm b/lib/AnswerHash.pm old mode 100755 new mode 100644 index bcb247a9e5..368729b88f --- a/lib/AnswerHash.pm +++ b/lib/AnswerHash.pm @@ -265,7 +265,7 @@ The entry $rh_ans->{error_flag} is set to "FLAG". The catch_error and clear_error methods use this entry. -message is a descriptive message for the end user, defining what error occured. +message is a descriptive message for the end user, defining what error occurred. =head3 catch_error @@ -597,7 +597,7 @@ sub evaluate { eval(q!main::DEBUG_MESSAGE( `

    final result:

    `, pretty_print($rh_ans,'html'))!) if defined($self->{debug}) and $self->{debug} > 0; - # re-refrence $rh_ans; + # re-reference $rh_ans; $self->{rh_ans} = $rh_ans; $rh_ans; } diff --git a/lib/AnswerIO.pm b/lib/AnswerIO.pm index 569767a56b..49bd852cad 100644 --- a/lib/AnswerIO.pm +++ b/lib/AnswerIO.pm @@ -30,7 +30,7 @@ use strict; sub saveAnswerToFile { my $logFileID = shift; my $string = shift; - # We want to allow acces only to predetermined files + # We want to allow access only to predetermined files # We accomplish this by translating legal IDs into a file name my $rh_allowableFiles = { diff --git a/lib/Applet.pm b/lib/Applet.pm index 78f7e9789b..8587a87fae 100644 --- a/lib/Applet.pm +++ b/lib/Applet.pm @@ -265,13 +265,13 @@ The applets initialization method is as follows: =head3 Submit sequence -When the WW question submit button is pressed the form containing the WW question calles the +When the WW question submit button is pressed the form containing the WW question calls the JavaScript "submitAction()" which then asks each of the applets on the page to perform its submit action which consists of -- If the applet is to be reinitialized (appletName_state contains - restart_applet) then the HTML elements appletName_state and - previous_appletName_state are set to restart_applet to be interpreted by the + restart_applet) then the HTML element appletName_state + is set to restart_applet to be interpreted by the next setState command. -- Otherwise getState() from the applet and save it to the HTML input element appletName_state. diff --git a/lib/ChoiceList.pm b/lib/ChoiceList.pm index 5955142a67..ff56910c34 100644 --- a/lib/ChoiceList.pm +++ b/lib/ChoiceList.pm @@ -351,7 +351,7 @@ sub cmp { return @answers; } -#Match and Select return references to arrays while Multiple justs returns a string +#Match and Select return references to arrays while Multiple just returns a string #so Match and Select use ra_correct_ans while Multiple uses correct_ans sub correct_ans { warn "Match and/or Select do not use correct_ans.\nYou should use ra_correct_ans instead."; @@ -419,7 +419,7 @@ sub getRandoms { my @remaining = complement([ 0 .. $N - 1 ], [@fixed_choices]); my @slice = @fixed_choices; - push(@slice, @remaining[ $self->NchooseK(scalar(@remaining), $K) ]); #slice of remaing choices + push(@slice, @remaining[ $self->NchooseK(scalar(@remaining), $K) ]); #slice of remaining choices @slice = @slice[ $self->NchooseK(scalar(@slice), scalar(@slice)) ]; #randomize the slice (the questions) #shuffle will be used to randomize the answers a second time (so they don't coincide with the questions) @@ -454,8 +454,8 @@ sub makeLast { push(@{ $self->{selected_a} }, @input); $self->condense(); #make sure that the user has not accidentally forced a duplicate answer - #note: condense was changed to eliminate the first occurence of a duplicate - #instead of the last occurence so that it could be used in this case and + #note: condense was changed to eliminate the first occurrence of a duplicate + #instead of the last occurrence so that it could be used in this case and #would not negate the fact that one of the answers needs to be at the end } @@ -526,7 +526,7 @@ sub condense { } #because we just changed the element that $outer points to - #we need to run throught the loop to make sure that the new value at $outer has + #we need to run through the loop to make sure that the new value at $outer has #no duplicates as well #This means that we don't want to increment either counter (and we need to reset $inner) $repeat = 1; diff --git a/lib/Chromatic.pm b/lib/Chromatic.pm index 1c7880b1b7..ab15642ecd 100644 --- a/lib/Chromatic.pm +++ b/lib/Chromatic.pm @@ -355,7 +355,7 @@ sub matrix_graph { return @matrix; } -# $graph input is a string adjacency matrix with rows terminted with semicolons +# $graph input is a string adjacency matrix with rows terminated with semicolons # and entries of each row separated by a space. sub ChromNum { my $graph = shift; diff --git a/lib/Complex1.pm b/lib/Complex1.pm index 82b52bf25f..b5a2658839 100644 --- a/lib/Complex1.pm +++ b/lib/Complex1.pm @@ -164,7 +164,7 @@ sub cplx { my ($re, $im) = @_; return $package->make(defined $re ? $re : 0, defined $im ? $im : 0); } -# cplx adn cplxe changed by MEG +# cplx and cplxe changed by MEG # # cplxe # diff --git a/lib/Distributions.pm b/lib/Distributions.pm index 4312065cbc..c13a4d1e14 100644 --- a/lib/Distributions.pm +++ b/lib/Distributions.pm @@ -551,8 +551,8 @@ Statistics::Distributions - Perl module for calculating probabilities and critic =head1 DESCRIPTION -This Perl module calulates percentage points (6 significant digits) of the u (standard normal) distribution, -the student's t distribution, the chi-square distribution and the F distribution. +This Perl module calculates percentage points (6 significant digits) of the u (standard normal) distribution, +the student's t distribution, the chi-square distribution and the F distribution. It can also calculate the upper probability (6 significant digits) of the u (standard normal), the chi-square, the t and the F distribution. @@ -567,7 +567,7 @@ If you are interested in more precise algorithms you could look at: =head1 AUTHOR Michael Kospach, mike.perl@gmx.at -Nice formating, simplification and bug repair by Matthias Trautner Kromann, mtk@id.cbs.dk +Nice formatting, simplification and bug repair by Matthias Trautner Kromann, mtk@id.cbs.dk =cut diff --git a/lib/DragNDrop.pm b/lib/DragNDrop.pm index 0f3e929d55..9192bd7a01 100644 --- a/lib/DragNDrop.pm +++ b/lib/DragNDrop.pm @@ -65,8 +65,8 @@ drag and drop bucket when clicked on. If the C option is defined, then buckets for which an explicit label is not provided will be will be created with the label with the C<%s> in the string replaced with the bucket number in the pool. This also -applies to new buckets that are added by JavaScript. An example value for this -option is C<< 'Subset %s' >>. +applies to new buckets that are added by the user via JavaScript if +C is 1. An example value for this option is C<'Subset %s'>. =item resetButtonText (Default: C<< 'Reset' >>) @@ -98,6 +98,77 @@ buckets, but not into it. Label shown for the universal set bucket if C is 1. +=item addFromUniversalText + +The aria announcement text format that is used when a universal set item is +added to a bucket via keyboard controls. The default format for this text is +C<'Item %1s in the universal set added as item %2s to list %3s.'>. Note that if +this is customized it must contain C<%1s>, C<%2s>, and C<%3s> as in the default +value. + +=item removeUniversalItemText + +The aria announcement text format that is used when an item from the universal +set that is in a bucket is removed via keyboard controls. The default format for +this text is C<'Item %1s removed from list %2s.'>. Note that if this is +customized it must contain C<%1s> and C<%2s> as in the default value. + +=item reorderText + +The aria announcement text format that is used when an item is moved up or down +in a bucket via keyboard controls. The default format for this text is +C<'Moved item %1s in list %2s to item %3s.'>. Note that if this is customized +it must contain C<%1s>, C<%2s>, and C<%3s> as in the default value. + +=item moveText + +The aria announcement text format that is used when an item is moved from one +bucket to another via keyboard controls. The default format for this text is +C<'Moved item %1s in list %2s to item %3s in list %4s.'>. Note that if this is +customized it must contain C<%1s>, C<%2s>, C<%3s>, and C<%4s> as in the default +value. + +=item helpButtonText (Default: C<'Drag and Drop Help'>) + +The text shown on the button that opens the drag and drop help. + +=item closeHelpButtonText (Default: C<'Close Help'>) + +The text shown on the button that closes the drag and drop help. + +=item dragAndDropHelpText + +The help that is shown when the drag and drop help button is pressed. + +The default text is + +=over 4 + +Drag to reorganize items within lists or to move items to a different list. Tab +and shift-tab can be used to focus list items. The left and right arrow keys +move a focused list item to the list to the left or right. The up and down +arrow keys move a focused list item up and down inside a list. + +=back + +=item universalSetHelpText + +If the option C is set to 1, then this is shown before the +C in the help. + +The default text for this is + +=over 4 + +Drag items in the universal set to copy them to a list. Tab and shift-tab can +be used to focus universal set items. A focused item in the universal set can +be added to the first list with the right or down arrow keys, or added to the +last list with the left or up arrow keys. A focused item in a list can be +removed by using the left or right arrow key until it returns to the universal +set. + +=back + =back =head2 METHODS @@ -138,18 +209,41 @@ use PGcore; sub new { my ($self, $answerName, $itemList, $defaultBuckets, %options) = @_; + my $PG = eval('$main::PG'); + return bless { - answerName => $answerName, - itemList => $itemList, - defaultBuckets => $defaultBuckets, - allowNewBuckets => 0, - bucketLabelFormat => undef, - resetButtonText => 'Reset', - addButtonText => 'Add Bucket', - removeButtonText => 'Remove', - multicolsWidth => '300pt', - showUniversalSet => 0, - universalSetLabel => 'Universal Set', + answerName => $answerName, + itemList => $itemList, + defaultBuckets => $defaultBuckets, + allowNewBuckets => 0, + bucketLabelFormat => undef, + resetButtonText => $PG->maketext('Reset'), + addButtonText => $PG->maketext('Add Bucket'), + removeButtonText => $PG->maketext('Remove'), + multicolsWidth => '300pt', + showUniversalSet => 0, + universalSetLabel => $PG->maketext('Universal Set'), + addFromUniversalText => + $PG->maketext('Item [_1] in the universal set added as item [_2] to list [_3].', '%1s', '%2s', '%3s'), + removeUniversalItemText => $PG->maketext('Item [_1] removed from list [_2].', '%1s', '%2s'), + reorderText => $PG->maketext('Moved item [_1] in list [_2] to item [_3].', '%1s', '%2s', '%3s'), + moveText => + $PG->maketext('Moved item [_1] in list [_2] to item [_3] in list [_4].', '%1s', '%2s', '%3s', '%4s'), + helpButtonText => $PG->maketext('Drag and Drop Help'), + closeHelpButtonText => $PG->maketext('Close Help'), + dragAndDropHelpText => $PG->maketext( + 'Drag to reorganize items within lists or to move items to a different list. ' + . 'Tab and shift-tab can be used to focus list items. ' + . 'The left and right arrow keys move a focused list item to the list to the left or right. ' + . 'The up and down arrow keys move a focused list item up and down inside a list.' + ), + universalSetHelpText => $PG->maketext( + 'Drag items in the universal set to copy them to a list. ' + . 'Tab and shift-tab can be used to focus universal set items. ' + . 'A focused item in the universal set can be added to the first list with the right or down arrow ' + . 'keys, or added to the last list with the left or up arrow keys. A focused item in a list can ' + . 'be removed by using the left or right arrow key until it returns to the universal set.' + ), %options, }, ref($self) || $self; @@ -165,6 +259,10 @@ sub HTML { $out .= qq{ data-label-format="$self->{bucketLabelFormat}"} if $self->{bucketLabelFormat}; $out .= " data-show-universal-set" if $self->{showUniversalSet}; $out .= ' data-universal-set-label="' . PGcore::encode_pg_and_html($self->{universalSetLabel}) . '"'; + $out .= ' data-add-from-universal-text="' . PGcore::encode_pg_and_html($self->{addFromUniversalText}) . '"'; + $out .= ' data-remove-universal-item-text="' . PGcore::encode_pg_and_html($self->{removeUniversalItemText}) . '"'; + $out .= ' data-reorder-text="' . PGcore::encode_pg_and_html($self->{reorderText}) . '"'; + $out .= ' data-move-text="' . PGcore::encode_pg_and_html($self->{moveText}) . '"'; $out .= '>'; $out .= '
    $self->{resetButtonText}}; $out .= qq{} if ($self->{allowNewBuckets}); - $out .= '
    '; + $out .= ''; + + $out .= + '
    ' + . '} + . qq{' + . '
    '; + + $out .= ''; return $out; } diff --git a/lib/Fraction.pm b/lib/Fraction.pm index faf86e0268..5b280d9c4f 100644 --- a/lib/Fraction.pm +++ b/lib/Fraction.pm @@ -2,7 +2,7 @@ # # Fraction object # Keeps track of two variables- numerator and denominator. -# Has subroutines for basic arithmatic functions, for anything +# Has subroutines for basic arithmetic functions, for anything # more complicated, it can return a scalar value of # numerator/denominator. # VS 7/20/2000 @@ -44,7 +44,7 @@ Other methods =head1 SYNOPSIS The fraction object stores two variables, numerator and denominator. The basic -arithmatic methods listed above can be performed on a fraction, and it can return its own +arithmetic methods listed above can be performed on a fraction, and it can return its own scalar value for use with functions expecting a scalar (ie, sqrt($frac->scalar) ). =cut @@ -387,7 +387,7 @@ sub print_inline { # Internal Methods # Least Common Multiple -# Used in arithmatic methods to convert two fractions to common denominator +# Used in arithmetic methods to convert two fractions to common denominator # takes in two scalar values and returns their lcm sub lcm { my $self = shift; @@ -418,7 +418,7 @@ sub lcm { sub gcd { my $self = shift; - my $a = abs(shift); #absolute values because this will yeild the same gcd, + my $a = abs(shift); #absolute values because this will yield the same gcd, my $b = abs(shift); #but allows use of the mod operation if ($a < $b) { diff --git a/lib/Fun.pm b/lib/Fun.pm index 85e1100c9c..289a217d8a 100644 --- a/lib/Fun.pm +++ b/lib/Fun.pm @@ -77,7 +77,7 @@ domain of the function object is set to the domain of the graph. =item $fn = new Fun ( x_rule_ref, y_rule_ref ); -A parametric function object is created where the subroutines refered to by x_rule_ref and y_rule_ref define +A parametric function object is created where the subroutines referred to by x_rule_ref and y_rule_ref define the x and y outputs in terms of the input t. =item $fn = new Fun ( x_rule_ref, y_rule_ref, graph_ref ); @@ -87,7 +87,7 @@ of the function object is not adjusted. The domain's default value is (-1, 1). =back -=head2 Properites +=head2 Properties All of the properties are set using the construction $new_value = $fn->property($new_value) and read using $current_value = $fn->property() diff --git a/lib/LaTeXImage.pm b/lib/LaTeXImage.pm index 20808e424e..fc23244dfd 100644 --- a/lib/LaTeXImage.pm +++ b/lib/LaTeXImage.pm @@ -206,8 +206,17 @@ sub draw { system "cd $working_dir && " . WeBWorK::PG::IO::externalCommand('latex') . " --interaction=nonstopmode image-dvisvgm.tex > latex.stdout 2> /dev/null"; - move("$working_dir/image-dvisvgm.dvi", "$working_dir/image.dvi"); - chmod(oct(664), "$working_dir/image.dvi"); + + if (-r "$working_dir/image-dvisvgm.dvi") { + move("$working_dir/image-dvisvgm.dvi", "$working_dir/image.dvi"); + chmod(oct(664), "$working_dir/image.dvi"); + } else { + warn 'The dvi file was not created.'; + if (open(my $err_fh, '<', "$working_dir/latex.stdout")) { + while (my $error = <$err_fh>) { warn $error; } + close($err_fh); + } + } } else { warn "Can't open $working_dir/image-dvisvgm.tex for writing."; return ''; @@ -223,7 +232,16 @@ sub draw { system "cd $working_dir && " . WeBWorK::PG::IO::externalCommand('latex2pdf') . " --interaction=nonstopmode image.tex > latex.stdout 2> /dev/null"; - chmod(oct(664), "$working_dir/image.pdf"); + + if (-r "$working_dir/image.pdf") { + chmod(oct(664), "$working_dir/image.pdf"); + } else { + warn 'The pdf file was not created.'; + if (open(my $err_fh, '<', "$working_dir/latex.stdout")) { + while (my $error = <$err_fh>) { warn $error; } + close($err_fh); + } + } } else { warn "Can't open $working_dir/image.tex for writing."; return ''; @@ -231,39 +249,19 @@ sub draw { } # Make derivatives of the dvi - if (($ext eq 'svg' || $ext eq 'tgz') && $svgMethod eq 'dvisvgm') { - if (-r "$working_dir/image.dvi") { - $self->use_svgMethod($working_dir); - } else { - warn "The dvi file was not created."; - if (open(my $err_fh, "<", "$working_dir/latex.stdout")) { - while (my $error = <$err_fh>) { - warn $error; - } - close($err_fh); - } - } + if (($ext eq 'svg' || $ext eq 'tgz') && $svgMethod eq 'dvisvgm' && -r "$working_dir/image.dvi") { + $self->use_svgMethod($working_dir); } # Make derivatives of the pdf - if (($svgMethod ne 'dvisvgm' || $ext ne 'svg') && $ext ne 'pdf') { - if (-r "$working_dir/image.pdf") { - if (($ext eq 'svg' || $ext eq 'tgz') && $svgMethod ne 'dvisvgm') { - $self->use_svgMethod($working_dir); - } - if ($ext eq 'tgz') { - $self->use_convert($working_dir, "png"); - } elsif ($ext ne 'svg' && $ext ne 'pdf') { - $self->use_convert($working_dir, $ext); - } - } else { - warn "The pdf file was not created."; - if (open(my $err_fh, "<", "$working_dir/latex.stdout")) { - while (my $error = <$err_fh>) { - warn $error; - } - close($err_fh); - } + if (($svgMethod ne 'dvisvgm' || $ext ne 'svg') && $ext ne 'pdf' && -r "$working_dir/image.pdf") { + if (($ext eq 'svg' || $ext eq 'tgz') && $svgMethod ne 'dvisvgm') { + $self->use_svgMethod($working_dir); + } + if ($ext eq 'tgz') { + $self->use_convert($working_dir, 'png'); + } elsif ($ext ne 'svg' && $ext ne 'pdf') { + $self->use_convert($working_dir, $ext); } } @@ -300,7 +298,7 @@ sub use_svgMethod { # Validate svgMethod against known SVG converters to prevent command injection. my $method = $self->svgMethod; if ($method eq 'dvisvgm') { - system WeBWorK::PG::IO::externalCommand('dvisvgm'), "$working_dir/image.dvi", '--no-fonts', + system WeBWorK::PG::IO::externalCommand('dvisvgm'), "$working_dir/image.dvi", '--no-fonts', '--verbosity=0', "--output=$working_dir/image.svg"; } elsif ($method eq 'pdf2svg') { system WeBWorK::PG::IO::externalCommand('pdf2svg'), "$working_dir/image.pdf", "$working_dir/image.svg"; diff --git a/lib/Label.pm b/lib/Label.pm index e987335aa0..ab9a28b518 100644 --- a/lib/Label.pm +++ b/lib/Label.pm @@ -22,7 +22,7 @@ This module defines labels for the graph objects (WWPlot). $label1 = new Label($x_value, $y_value, $label_string, $label_color, @options) $options is an array with (*'d defaults) - one of 'left'*, 'center', 'right' (horizontal alignment) - - one of 'bottom', 'center', 'top'* (verical alignment) + - one of 'bottom', 'center', 'top'* (vertical alignment) - one of 'horizontal'*, 'vertical' (orientation) - one of 'small', 'large', 'mediumbold'*, 'tiny', 'giant' (which gd font to use) Note the alignment specifications are relative to the English reading of the string, @@ -87,7 +87,7 @@ sub _initialize { elsif ($j eq 'center') { $self->lr_nudge(-(length($self->str)) / 2); } elsif ($j eq 'middle') { $self->tb_nudge(-0.5); } elsif ($j eq 'vertical') { $self->orientation($j); } - #there are only five avialble fonts: http://search.cpan.org/~rurban/GD-2.68/lib/GD.pm#Font_Utilities + #there are only five available fonts: http://search.cpan.org/~rurban/GD-2.68/lib/GD.pm#Font_Utilities elsif ($j eq 'small') { $self->font(GD::gdSmallFont); } elsif ($j eq 'large') { $self->font(GD::gdLargeFont); } elsif ($j eq 'tiny') { $self->font(GD::gdTinyFont); } diff --git a/lib/List.pm b/lib/List.pm index e7059dc741..630a9e411b 100644 --- a/lib/List.pm +++ b/lib/List.pm @@ -351,7 +351,7 @@ sub cmp { return @answers; } -#Match and Select return references to arrays while Multiple justs returns a string +#Match and Select return references to arrays while Multiple just returns a string #so Match and Select use ra_correct_ans while Multiple uses correct_ans sub correct_ans { warn "Match and/or Select do not use correct_ans.\nYou should use ra_correct_ans instead."; @@ -419,7 +419,7 @@ sub getRandoms { my @remaining = complement([ 0 .. $N - 1 ], [@fixed_choices]); my @slice = @fixed_choices; - push(@slice, @remaining[ $self->NchooseK(scalar(@remaining), $K) ]); #slice of remaing choices + push(@slice, @remaining[ $self->NchooseK(scalar(@remaining), $K) ]); #slice of remaining choices @slice = @slice[ $self->NchooseK(scalar(@slice), scalar(@slice)) ]; #randomize the slice (the questions) #shuffle will be used to randomize the answers a second time (so they don't coincide with the questions) @@ -454,8 +454,8 @@ sub makeLast { push(@{ $self->{selected_a} }, @input); $self->condense(); #make sure that the user has not accidentally forced a duplicate answer - #note: condense was changed to eliminate the first occurence of a duplicate - #instead of the last occurence so that it could be used in this case and + #note: condense was changed to eliminate the first occurrence of a duplicate + #instead of the last occurrence so that it could be used in this case and #would not negate the fact that one of the answers needs to be at the end } @@ -529,7 +529,7 @@ sub condense { } #because we just changed the element that $outer points to - #we need to run throught the loop to make sure that the new value at $outer has + #we need to run through the loop to make sure that the new value at $outer has #no duplicates as well #This means that we don't want to increment either counter (and we need to reset $inner) $repeat = 1; @@ -575,7 +575,7 @@ sub condense { # my $out = ''; # if ( not ref($r_input) ) { # $out = $r_input; # not a reference -# } elsif ("$r_input" =~/hash/i ) { # this will pick up objects whose '$self' is hash and so works better than ref($r_iput). +# } elsif ("$r_input" =~/hash/i ) { # this will pick up objects whose '$self' is hash and so works better than ref($r_input). # local($^W) = 0; # $out .= "$r_input " .""; # foreach my $key (sort keys %$r_input ) { diff --git a/lib/Matrix.pm b/lib/Matrix.pm index eec59c5984..fbaefb9f83 100644 --- a/lib/Matrix.pm +++ b/lib/Matrix.pm @@ -106,7 +106,7 @@ sub R { $R_matrix; } -sub PL { # use this permuation on the left PL*L*R*PR =M +sub PL { # use this permutation on the left PL*L*R*PR =M my $matrix = shift; my $rows = $matrix->[1]; my $cols = $rows; @@ -117,7 +117,7 @@ sub PL { # use this permuation on the left PL*L*R*PR =M $PL_matrix; } -sub PR { # use this permuation on the right PL*L*R*PR =M +sub PR { # use this permutation on the right PL*L*R*PR =M my $matrix = shift; my $cols = $matrix->[2]; my $rows = $cols; @@ -384,7 +384,7 @@ sub copy { } ################################################################### -# MEG added 6/25/03 to accomodate complex entries +# MEG added 6/25/03 to accommodate complex entries =head2 conj @@ -456,7 +456,7 @@ sub decompose_LR { my ($swap); my ($temp); my $rh_options = $matrix->[$MatrixReal1::OPTION_ENTRY]; - # FIXEME Why won't this work on non-square matrices? + # FIXME: Why won't this work on non-square matrices? # croak "MatrixReal1::decompose_LR(): matrix is not quadratic" # unless ($rows == $cols); # croak "MatrixReal1::decompose_LR(): matrix has more rows than columns" diff --git a/lib/MatrixReal1.pm b/lib/MatrixReal1.pm index a9bb2725d7..4584cec114 100644 --- a/lib/MatrixReal1.pm +++ b/lib/MatrixReal1.pm @@ -3226,7 +3226,7 @@ The primary property of an eigenvalue I and an eigenvector B is of course that: B * B = I * B. The method uses a Householder reduction to tridiagonal form -followed by a QL algoritm with implicit shifts on this +followed by a QL algorithm with implicit shifts on this tridiagonal. (The tridiagonal matrix is kept internally in a compact form in this routine to save memory.) In fact, this routine wraps the householder() and @@ -3246,7 +3246,7 @@ the I by I real I matrix B contained in $matrix to tridiagonal form. On output, B is a symmetric tridiagonal matrix (only diagonal and off-diagonal elements are non-zero) and B -is an I matrix performing the tranformation +is an I matrix performing the transformation between B and B (C<$M == $Q * $T * ~$Q>). =item * @@ -3385,7 +3385,7 @@ Example: Boolean test -Tests wether there is at least one non-zero element in the matrix. +Tests whether there is at least one non-zero element in the matrix. Example: @@ -3395,7 +3395,7 @@ Example: Negated boolean test -Tests wether the matrix contains only zero's. +Tests whether the matrix contains only zero's. Examples: diff --git a/lib/PGUtil.pm b/lib/PGUtil.pm index c6a7c08cff..9338c29002 100644 --- a/lib/PGUtil.pm +++ b/lib/PGUtil.pm @@ -89,7 +89,7 @@ sub pretty_print_html { # provides html output -- NOT a method if (!$ref) { return $r_input =~ s/' + return '
    ' . ($ref eq 'HASH' ? '' : '
    $label, - ans_eval => undef, # usually an AnswerEvaluator, sometimes a CODE - response => new PGresponsegroup($label), # A PGresponse object which holds the responses - # which make up the answer + ans_eval => undef, # usually an AnswerEvaluator, sometimes a CODE + response => PGresponsegroup->new($label), # A PGresponse object which holds the responses + # which make up the answer active => 1, # whether this answer group is currently active (for multistate problems) - - @_, + %options }; bless $self, $class; return $self; @@ -104,7 +104,7 @@ sub insert { # add new values to PGanswergroup keys preserve existing values $self; } -sub replace { # add new values ot PGanswergroup, overwriting existing values when duplicated +sub replace { # add new values to PGanswergroup, overwriting existing values when duplicated my $self = shift; my @in = @_; my %hash = (); diff --git a/lib/PGcore.pm b/lib/PGcore.pm old mode 100755 new mode 100644 index 15a7e69b70..86ba95c9df --- a/lib/PGcore.pm +++ b/lib/PGcore.pm @@ -10,8 +10,6 @@ BEGIN { $ENV{PG_VERSION} = $PGcore::PG_VERSION || 'unknown'; } -our $internal_debug_messages = []; - use PGanswergroup; use PGresponsegroup; use PGrandom; @@ -154,7 +152,7 @@ named answer is associated with its answer blank by name. =item * "Extra" answers: Names of answer blanks that do not have a 1-to-1 -correspondance to an answer evaluator. For example, in matrix problems, there +correspondence to an answer evaluator. For example, in matrix problems, there will be several input fields that correspond to the same answer evaluator. =back @@ -165,7 +163,7 @@ This file is automatically loaded into the namespace of every PG problem. The macros within can then be called to define the structure of the problem. DOCUMENT() should be the first executable statement in any problem. It -initializes vriables and defines the problem environment. +initializes variables and defines the problem environment. ENDDOCUMENT() must be the last executable statement in any problem. It packs up the results of problem processing for delivery back to WeBWorK. @@ -418,7 +416,7 @@ sub new_ans_name { sub record_ans_name { my ($self, $label, $value) = @_; - my $response_group = new PGresponsegroup($label, $label, $value); + my $response_group = PGresponsegroup->new($label, $label, $value); if (ref($self->{PG_ANSWERS_HASH}{$label}) eq 'PGanswergroup') { # This should really never happen. Should this warn if it does? @@ -524,7 +522,7 @@ sub encode_base64 ($;$) { } ##### -# This macro encodes HTML, EV3, and PGML special caracters using html codes +# This macro encodes HTML, EV3, and PGML special characters using html codes # This should be done for any variable which contains student input and is # printed to a screen or interpreted by EV3. @@ -705,9 +703,8 @@ sub directoryFromPath { } sub AskSage { - my $self = shift; - my $python = shift; - my $options = shift; + my ($self, $python, $options) = @_; + $options = {} unless ref $options eq 'HASH'; $options->{curlCommand} = WeBWorK::PG::IO::externalCommand('curl'); WeBWorK::PG::IO::AskSage($python, $options); } @@ -732,15 +729,6 @@ To report the messages use: These are used in Problem.pm for example to report any errors. -There is also - - $PG->internal_debug_message() - $PG->get_internal_debug_message - $PG->clear_internal_debug_messages(); - -There were times when things were buggy enough that only the internal_debug_message which are not saved -inside the PGcore object would report. - =cut sub debug_message { @@ -763,21 +751,6 @@ sub get_warning_messages { $self->{WARNING_messages}; } -sub internal_debug_message { - my ($self, @str) = @_; - push @$internal_debug_messages, @str; -} - -sub get_internal_debug_messages { - my $self = shift; - $internal_debug_messages; -} - -sub clear_internal_debug_messages { - my $self = shift; - $internal_debug_messages = []; -} - sub DESTROY { # doing nothing about destruction, hope that isn't dangerous } diff --git a/lib/PGloadfiles.pm b/lib/PGloadfiles.pm index 65f4932d5b..06fbed03f0 100644 --- a/lib/PGloadfiles.pm +++ b/lib/PGloadfiles.pm @@ -39,7 +39,7 @@ modifying existing macros. I Modifying existing macros might break other standard macros or problems which -depend on the unmodified behavior of these macors so do this with great caution. +depend on the unmodified behavior of these macros so do this with great caution. In addition problems which use new macros defined in these files or which depend on the modified behavior of existing macros will not work in other courses unless the macros are also transferred to the new course. It helps to document @@ -135,7 +135,7 @@ sub loadMacros { } my $macro_file_name = $fileName; $macro_file_name =~ s/\.pl//; # trim off the extension - $macro_file_name =~ s/\.pg//; # sometimes the extension is .pg (e.g. CAPA files) + $macro_file_name =~ s/\.pg//; # sometimes the extension is .pg my $init_subroutine_name = "_${macro_file_name}_init"; $init_subroutine_name =~ s![^a-zA-Z0-9_]!_!g; # remove dangerous chars diff --git a/lib/PGresponsegroup.pm b/lib/PGresponsegroup.pm index 82f2d074c4..f85f5ddb51 100644 --- a/lib/PGresponsegroup.pm +++ b/lib/PGresponsegroup.pm @@ -22,11 +22,13 @@ use PGUtil qw(not_null); # Optionally append label/response pairs. sub new { my ($class, $answergroup_label, @responses) = @_; + my $pg = eval('$main::PG'); my $self = bless { answergroup_label => $answergroup_label, # enclosing answergroup that created this responsegroup response_order => [], # response labels responses => {}, # response label/response value pair, # value could be an arrayref in the case of radio or checkbox groups + WARNING_messages => $pg->{WARNING_messages} }, $class; $self->append_responses(@responses); return $self; @@ -45,12 +47,11 @@ sub append_response { ? [ map { [ $_ => $response_value->{$_} ] } keys %$response_value ] : $response_value; } else { - $self->internal_debug_message( - "PGresponsegroup::append_response error: there is already an answer labeled $response_label", - caller(2), "\n"); + $self->warning_message( + qq{PGresponsegroup::append_response error: There is already an answer labeled "$response_label".}); } } else { - $self->internal_debug_message('PGresponsegroup::append_response error: undefined or empty response label'); + $self->warning_message('PGresponsegroup::append_response error: Undefined or empty response label.'); } return; } @@ -77,18 +78,19 @@ sub replace_response { } # Extend the response to an array for this response label entry. This is used for check boxes and radio buttons. This -# converts the reponse value into an array of label/value pairs if it is a hash to begin with. Otherwise it just adds a +# converts the response value into an array of label/value pairs if it is a hash to begin with. Otherwise it just adds a # label/value pair to the existing array. sub extend_response { my ($self, $response_label, $new_value_key, $selected) = @_; - if (defined $self->{responses}{$response_label}) { + if (defined $response_label && defined $self->{responses}{$response_label}) { my $response_value = $self->{responses}{$response_label}; $response_value //= []; if (ref($response_value) !~ /^(HASH|ARRAY)$/) { - $self->internal_debug_message("PGresponsegroup::extend_response: error in extending response ", - ref($response_value), $response_value); + $self->warning_message('PGresponsegroup::extend_response error: Invalid value type "' + . (ref($response_value) || 'scalar') + . qq{" for $response_label.}); $response_value = [ [ $response_value => $selected ] ]; } @@ -99,7 +101,12 @@ sub extend_response { $self->{responses}{$response_label} = $response_value; return $response_value; } else { - $self->internal_debug_message("PGresponsegroup::extend_response: response label |$response_label| not defined"); + if (defined $response_label) { + $self->warning_message( + qq{PGresponsegroup::extend_response error: Response label "$response_label" not defined.}); + } else { + $self->warning_message('PGresponsegroup::extend_response error: Response label not provided.'); + } return; } } diff --git a/lib/Parser.pm b/lib/Parser.pm index c943441e33..00e0fc7257 100644 --- a/lib/Parser.pm +++ b/lib/Parser.pm @@ -6,7 +6,7 @@ no strict "refs"; BEGIN { # # Map class names to packages (added to Context, and - # can be overriden to customize the parser) + # can be overridden to customize the parser) # our $class = { Formula => 'Value::Formula' }; @@ -191,7 +191,7 @@ sub push { push(@{ (shift)->{stack} }, @_) } sub state { (shift)->top->{type} } # -# Report an error at a given possition (if possible) +# Report an error at a given position (if possible) # sub Error { my $self = shift; @@ -274,7 +274,7 @@ sub pushBlankOperand { # Apply it to the top operand # Otherwise (binary operator) # Convert the space operator to explicit multiplication -# Save the opertor on the stack +# Save the operator on the stack # Otherwise, (top is not an operand) # If the operator is an explicit one or the top is a function # Call Op again to report the error, or to apply @@ -713,7 +713,7 @@ sub eval { ################################################## # -# Removes redundent items (like x+-y, 0+x and 1*x, etc) +# Removes redundant items (like x+-y, 0+x and 1*x, etc) # using the provided flags # sub reduce { diff --git a/lib/Parser/BOP.pm b/lib/Parser/BOP.pm index d79539766f..ab586f9644 100644 --- a/lib/Parser/BOP.pm +++ b/lib/Parser/BOP.pm @@ -60,7 +60,7 @@ sub _check { } ################################################## # -# Evaluate the left and right operands and peform the +# Evaluate the left and right operands and perform the # required operation on the results. # sub eval { diff --git a/lib/Parser/BOP/equality.pm b/lib/Parser/BOP/equality.pm index c894cd92c0..00d0ed0fcf 100644 --- a/lib/Parser/BOP/equality.pm +++ b/lib/Parser/BOP/equality.pm @@ -32,7 +32,7 @@ sub _eval { } # -# Remove redundent minuses +# Remove redundant minuses # sub _reduce { my $self = shift; diff --git a/lib/Parser/BOP/underscore.pm b/lib/Parser/BOP/underscore.pm index f5a495468a..8a106d66fe 100644 --- a/lib/Parser/BOP/underscore.pm +++ b/lib/Parser/BOP/underscore.pm @@ -48,7 +48,7 @@ sub _eval { # # If the right-hand side is constant and the left is a list -# extact the given coordinate(s). Return empty lists +# extract the given coordinate(s). Return empty lists # if we run past the end of the coordinates. Return # a simpler extraction if a portion of the extraction # can be performed. diff --git a/lib/Parser/Context/Default.pm b/lib/Parser/Context/Default.pm index c182fdcd9c..d66af413ba 100644 --- a/lib/Parser/Context/Default.pm +++ b/lib/Parser/Context/Default.pm @@ -435,6 +435,7 @@ $flags = { reduceConstantFunctions => 1, # 1 = compute function values of constants showExtraParens => 1, # 1 = add useful parens, 2 = make things painfully unambiguous stringifyNoBrackets => 0, # 1 = only use parentheses not brackets when stringifying + stringifyAbsAsFunction => 0, # 1 = abs(x) is stringified as abs(x) instead of |x| formatStudentAnswer => 'evaluated', # or 'parsed' or 'reduced' allowMissingOperands => 0, # 1 is used by Typeset context allowMissingFunctionInputs => 0, # 1 is used by Typeset context diff --git a/lib/Parser/Context/Variables.pm b/lib/Parser/Context/Variables.pm index 243c24124c..9b364c057e 100644 --- a/lib/Parser/Context/Variables.pm +++ b/lib/Parser/Context/Variables.pm @@ -8,7 +8,7 @@ our @ISA = qw(Value::Context::Data); # # The named types for variables -# (you can use arbitary types by supplying an +# (you can use arbitrary types by supplying an # instance of the type rather than a name) # our %type = ( diff --git a/lib/Parser/Function.pm b/lib/Parser/Function.pm index 213aa4b895..b6d2045ac0 100644 --- a/lib/Parser/Function.pm +++ b/lib/Parser/Function.pm @@ -287,7 +287,7 @@ sub getVariables { # # Produce the string form. # -# Put parentheses around the funciton call if +# Put parentheses around the function call if # the function call is on the left of the parent operation # and the precedence of the parent is higher than function call # (e.g., powers, etc.) diff --git a/lib/Parser/Function/numeric.pm b/lib/Parser/Function/numeric.pm index d9540e4198..79edcc75a3 100644 --- a/lib/Parser/Function/numeric.pm +++ b/lib/Parser/Function/numeric.pm @@ -82,6 +82,8 @@ $Parser::reduce->{'ln(e^x)'} = 1; # sub string { my $self = shift; + return 'abs(' . $self->{params}[0]->string . ')' + if $self->{name} eq 'abs' && $self->context->flag('stringifyAbsAsFunction'); return '|' . $self->{params}[0]->string . '|' if $self->{name} eq 'abs'; return $self->SUPER::string(@_); } diff --git a/lib/Parser/Legacy/LimitedComplex.pm b/lib/Parser/Legacy/LimitedComplex.pm index c9d10b506d..19242b02cf 100644 --- a/lib/Parser/Legacy/LimitedComplex.pm +++ b/lib/Parser/Legacy/LimitedComplex.pm @@ -233,7 +233,7 @@ package Parser::Legacy::LimitedComplex; my $context = $Parser::Context::Default::context{Complex}->copy; $Parser::Context::Default::context{LimitedComplex} = $context; -$context->{name} = "LimtedComplex"; +$context->{name} = "LimitedComplex"; # # Override operator classes @@ -270,7 +270,7 @@ $context->flags->set(complex_format => 'either'); $context = $context->copy; $Parser::Context::Default::context{'LimitedComplex-cartesian'} = $context; $context->flags->set(complex_format => 'cartesian'); -$context->{name} = "LimtedComplex-cartesian"; +$context->{name} = "LimitedComplex-cartesian"; ################################################## @@ -278,14 +278,14 @@ $context = $context->copy; $Parser::Context::Default::context{'LimitedComplex-cartesian-strict'} = $context; $context->flags->set(strict_numeric => 1); $context->functions->disable('All'); -$context->{name} = "LimtedComplex-cartesian-strinct"; +$context->{name} = "LimitedComplex-cartesian-strinct"; ################################################## $context = $Parser::Context::Default::context{'LimitedComplex'}->copy; $Parser::Context::Default::context{'LimitedComplex-polar'} = $context; $context->flags->set(complex_format => 'polar'); -$context->{name} = "LimtedComplex-polar"; +$context->{name} = "LimitedComplex-polar"; ################################################## @@ -293,7 +293,7 @@ $context = $context->copy; $Parser::Context::Default::context{'LimitedComplex-polar-strict'} = $context; $context->flags->set(strict_numeric => 1); $context->functions->disable('All'); -$context->{name} = "LimtedComplex-polar-strict"; +$context->{name} = "LimitedComplex-polar-strict"; ################################################## @@ -301,7 +301,7 @@ $context = $Parser::Context::Default::context{'LimitedComplex'}->copy; $Parser::Context::Default::context{'LimitedComplex-strict'} = $context; $context->flags->set(strict_numeric => 1); $context->functions->disable('All'); -$context->{name} = "LimtedComplex-strict"; +$context->{name} = "LimitedComplex-strict"; ################################################## diff --git a/lib/Parser/Legacy/LimitedNumeric.pm b/lib/Parser/Legacy/LimitedNumeric.pm index 48a5b5eac0..9500f81100 100644 --- a/lib/Parser/Legacy/LimitedNumeric.pm +++ b/lib/Parser/Legacy/LimitedNumeric.pm @@ -7,7 +7,7 @@ # and one for fractions of integers. Select them using # one of the following commands: # -# Context("LimiteNumeric"); +# Context("LimitedNumeric"); # Context("LimitedNumeric-Fraction"); # # There is also a third version, which is a strict fraction diff --git a/lib/Parser/Legacy/NumberWithUnits.pm b/lib/Parser/Legacy/NumberWithUnits.pm index 1a357ca140..fd4929e594 100644 --- a/lib/Parser/Legacy/NumberWithUnits.pm +++ b/lib/Parser/Legacy/NumberWithUnits.pm @@ -7,7 +7,7 @@ package Parser::Legacy::ObjectWithUnits; -# Refrences to problem specific copies of %Units::fundamental_units +# References to problem specific copies of %Units::fundamental_units # and %Units::known_units. These should be passed to any Units function call. # They are set by the initializeUnits sub my $fundamental_units = ''; diff --git a/lib/Parser/Legacy/PGcomplexmacros.pl b/lib/Parser/Legacy/PGcomplexmacros.pl index 153be18195..f7120998a9 100644 --- a/lib/Parser/Legacy/PGcomplexmacros.pl +++ b/lib/Parser/Legacy/PGcomplexmacros.pl @@ -67,7 +67,7 @@ =head3 cplx_cmp # functions. The default display method is cartesian, for all methods, but if # the student answer is polar, even in part, then their answer will be displayed # that way. - # 'strict_polar' This is still under developement. The idea is to check to make sure that there + # 'strict_polar' This is still under development. The idea is to check to make sure that there # only a single term in front of the e and after it... but the method does not # check to make sure that the i is in the exponent, nor does it handle cases # where the polar has e** coefficients. @@ -286,7 +286,7 @@ sub original_cplx_cmp { # error checking, but that was removed in version 1.9 and it had been commented out # prior to that because it was always producing errors. This is because $correct_num_answer # usually is somethine like "1+4i", which will produce a "missing operation before 'i'" - # error, and "1-i" wil produce an "amiguous use of '-i' resolved as '-&i'" message. + # error, and "1-i" will produce an "ambiguous use of '-i' resolved as '-&i'" message. # You probably need a call to check_syntax and the other filters that are used on # the student answer first. (Unless the item is already a reference to a Complex, # in which canse you should just accept it.) @@ -414,12 +414,12 @@ =head3 multi_cmp # # Checks a comma separated string of items against an array of evaluators. # For example this is useful for checking all of the complex roots of an equation. - # Each student answer must be evaluated as correct by a DISTINCT answer evalutor. + # Each student answer must be evaluated as correct by a DISTINCT answer evaluator. # # This answer checker will only work reliably if each answer checker corresponds # to a distinct correct answer. For example if one answer checker requires # any positive number, and the second requires the answer 1, then 1,2 might - # be judged incorrect since 1, satisifes the first answer checker, but 2 doesn't + # be judged incorrect since 1, satisfies the first answer checker, but 2 doesn't # satisfy the second. 2,1 would work however. Avoid this type of use!! # # Including backtracking to fit the answers as best possible to each answer evaluator @@ -517,7 +517,7 @@ =head2 Utility functions =cut -# Output is text displaying the complex numver in "e to the i theta" form. The +# Output is text displaying the complex number in "e to the i theta" form. The # formats for the argument theta is determined by the option C and the # format for the modulus is determined by the C option. @@ -650,8 +650,8 @@ sub is_a_numeric_polar { } } -#this subroutine mearly captures what is before and after the "e**" it does not verify that the "i" is there, or in the -#exponent this must eventually be addresed +#this subroutine merely captures what is before and after the "e**" it does not verify that the "i" is there, or in the +#exponent this must eventually be addressed sub is_a_polar { my ($num, %options) = @_; my $process_ans_hash = (ref($num) eq 'AnswerHash') ? 1 : 0; @@ -689,9 +689,9 @@ sub is_a_polar { =head4 single_term() - # This subroutine takes in a string, which is a mathematical expresion, and determines whether or not + # This subroutine takes in a string, which is a mathematical expression, and determines whether or not # it is a single term. This is accoplished using a stack. Open parenthesis pluses and minuses are all - # added onto the stack, and when a closed parenthesis is reached, the stack is popped untill the open + # added onto the stack, and when a closed parenthesis is reached, the stack is popped until the open # parenthesis is found. If the original was a single term, the stack should be empty after # evaluation. If there is anything left ( + or - ) then false is returned. # Of course, the unary operator "-" must be handled... if it is a unary operator, and not a regular - diff --git a/lib/Perl/Critic/Policy/PG/ProhibitBeginproblem.pm b/lib/Perl/Critic/Policy/PG/ProhibitBeginproblem.pm new file mode 100644 index 0000000000..72b002c9d0 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitBeginproblem.pm @@ -0,0 +1,36 @@ +package Perl::Critic::Policy::PG::ProhibitBeginproblem; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); + +use constant DESCRIPTION => 'The beginproblem function is called'; +use constant EXPLANATION => 'The beginproblem function no longer does anything and should be removed.'; +use constant SCORE => 5; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $) { + return unless $element eq 'beginproblem' && is_function_call($element); + return $self->violation(DESCRIPTION, { score => SCORE, explanation => EXPLANATION }, $element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitBeginproblem - The C function is +deprecated, no longer does anything, and should be removed from all problems. + +=head1 DESCRIPTION + +The C function is deprecated, no longer does anything, and should +be removed from all problems. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitContextStrings.pm b/lib/Perl/Critic/Policy/PG/ProhibitContextStrings.pm new file mode 100644 index 0000000000..309ee1a832 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitContextStrings.pm @@ -0,0 +1,40 @@ +package Perl::Critic::Policy::PG::ProhibitContextStrings; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); + +use constant DESCRIPTION => 'Context()->%s is called'; +use constant EXPLANATION => 'Context()->%s no longer necessary and should be removed.'; +use constant SCORE => 5; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $) { + return + unless ($element eq 'texStrings' || $element eq 'normalStrings') + && is_method_call($element) + && $element->parent =~ /^Context/; + return $self->violation(sprintf(DESCRIPTION, $element), + { score => SCORE, explanation => sprintf(EXPLANATION, $element) }, $element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::RequireContextStrings - C<< Context()->texStrings >> +and C<< Context->normalStrings >> calls are not necessary and should be removed. + +=head1 DESCRIPTION + +Calling C<< Context()->texStrings >> and C<< Context->normalStrings >> is no +longer necessary and should be removed from problems. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedCmp.pm b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedCmp.pm new file mode 100644 index 0000000000..4af90b468b --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedCmp.pm @@ -0,0 +1,85 @@ +package Perl::Critic::Policy::PG::ProhibitDeprecatedCmp; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'The deprecated %s method is called'; +use constant EXPLANATION => 'Convert the answer into a MathObject and use the cmp method of the object.'; +use constant SCORE => 55; + +use constant CMP_METHODS => { + str_cmp => 1, + std_str_cmp => 1, + std_str_cmp_list => 1, + std_cs_str_cmp => 1, + std_cs_str_cmp_list => 1, + strict_str_cmp => 1, + strict_str_cmp_list => 1, + unordered_str_cmp => 1, + unordered_str_cmp_list => 1, + unordered_cs_str_cmp => 1, + unordered_cs_str_cmp_list => 1, + ordered_str_cmp => 1, + ordered_str_cmp_list => 1, + ordered_cs_str_cmp => 1, + ordered_cs_str_cmp_list => 1, + num_cmp => 1, + num_rel_cmp => 1, + std_num_cmp => 1, + std_num_cmp_list => 1, + std_num_cmp_abs => 1, + std_num_cmp_abs_list => 1, + frac_num_cmp => 1, + frac_num_cmp_list => 1, + frac_num_cmp_abs => 1, + frac_num_cmp_abs_list => 1, + arith_num_cmp => 1, + arith_num_cmp_list => 1, + arith_num_cmp_abs => 1, + arith_num_cmp_abs_list => 1, + strict_num_cmp => 1, + strict_num_cmp_list => 1, + strict_num_cmp_abs => 1, + strict_num_cmp_abs_list => 1, + std_num_str_cmp => 1, + fun_cmp => 1, + function_cmp => 1, + function_cmp_up_to_constant => 1, + function_cmp_abs => 1, + function_cmp_up_to_constant_abs => 1, + adaptive_function_cmp => 1, + multivar_function_cmp => 1, + cplx_cmp => 1, + multi_cmp => 1, + radio_cmp => 1, + checkbox_cmp => 1, +}; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $document) { + return unless CMP_METHODS->{$element} && is_function_call($element); + return $self->violation(sprintf(DESCRIPTION, $element), { score => SCORE, explanation => EXPLANATION }, $element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitDeprecatedCmp - Use C instead of +the deprecated C methods. + +=head1 DESCRIPTION + +Convert answers into a C and use the C method of the object +instead of using any of the deprecated C methods such as C from +the L macro, C from the +L macro, or C from the +L macro. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMacros.pm b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMacros.pm new file mode 100644 index 0000000000..27bbd26512 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMacros.pm @@ -0,0 +1,45 @@ +package Perl::Critic::Policy::PG::ProhibitDeprecatedMacros; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); + +use constant DESCRIPTION => 'The deprecated macro %s is loaded'; +use constant EXPLANATION => 'Remove this macro and replace methods used from this macro with modern alternatives.'; +use constant SCORE => 10; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $) { + return unless $element eq 'loadMacros' && is_function_call($element); + my $deprecatedMacros = getDeprecatedMacros; + return unless $deprecatedMacros; + return map { + $self->violation( + sprintf(DESCRIPTION, $_->[0]->string), + { score => SCORE, explanation => EXPLANATION }, + $_->[0] + ) + } + grep { $deprecatedMacros->{ $_->[0]->string } } parse_arg_list($element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitDeprecatedMacros - Replace deprecated macro +usage with modern alternatives. + +=head1 DESCRIPTION + +All problems that use a deprecated macro (those in the C directory) +should be rewritten to use modern alternatives. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMultipleChoice.pm b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMultipleChoice.pm new file mode 100644 index 0000000000..bc38344461 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMultipleChoice.pm @@ -0,0 +1,55 @@ +package Perl::Critic::Policy::PG::ProhibitDeprecatedMultipleChoice; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); + +use constant DESCRIPTION => 'The deprecated %s function is called'; +use constant EXPLANATION => 'The deprecated %s function should be replaced with a modern alternative.'; +use constant SCORE => 20; +use constant SAMPLE_PROBLEMS => [ + [ 'Multiple Choice with Checkbox' => 'Misc/MultipleChoiceCheckbox' ], + [ 'Multiple Choice with Popup' => 'Misc/MultipleChoicePopup' ], + [ 'Multiple Choice with Radio Buttons' => 'Misc/MultipleChoiceRadio' ] +]; + +# Note that new_match_list is not in this list because there is not a modern alternative yet. +# The qa method is also not listed because it is needed with new_match_list. +use constant MULTIPLE_CHOICE_METHODS => { + new_checkbox_multiple_choice => 1, + new_multiple_choice => 1, + new_pop_up_select_list => 1, + new_select_list => 1 +}; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $) { + return unless MULTIPLE_CHOICE_METHODS->{$element} && is_function_call($element); + return $self->violation(sprintf(DESCRIPTION, $element), + { score => SCORE, explanation => sprintf(EXPLANATION, $element), sampleProblems => SAMPLE_PROBLEMS }, + $element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitDeprecatedMultipleChoice - Replace usage of +L multiple choice methods with the appropriate MathObject +multiple choice +macro. + +=head1 DESCRIPTION + +Replace usage of L multiple choice methods with the +appropriate modern multiple choice macro. For example, consider using +L, L, or L. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedWeightedGrader.pm b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedWeightedGrader.pm new file mode 100644 index 0000000000..77a0755ef2 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedWeightedGrader.pm @@ -0,0 +1,55 @@ +package Perl::Critic::Policy::PG::ProhibitDeprecatedWeightedGrader; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'The deprecated %s function is called'; +use constant EXPLANATION => 'The deprecated %s function should be replaced with a modern alternative.'; +use constant SCORE => 5; +use constant SAMPLE_PROBLEMS => [ [ 'Weighted Grader' => 'ProblemTechniques/WeightedGrader' ] ]; +use constant WEIGHTED_GRADER_METHODS => { + install_weighted_grader => 1, + WEIGHTED_ANS => 1, + NAMED_WEIGHTED_ANS => 1, + weight_ans => 1, + CREDIT_ANS => 1, +}; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $) { + return unless WEIGHTED_GRADER_METHODS->{$element} && is_function_call($element); + return $self->violation(sprintf(DESCRIPTION, $element), + { score => SCORE, explanation => sprintf(EXPLANATION, $element), sampleProblems => SAMPLE_PROBLEMS }, + $element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitDeprecatedWeightedGrader - The +L functionality is now included in the default + L, and +this macros is no longer needed. + +=head1 DESCRIPTION + +The default L +includes all the functionality of the L, and use of +this macro should be removed. Remove calling the function +C. Instead of calling C or +C, pass C<< weight => n >> to the C method. +In PGML use the following: + + [_]{$answer}{ cmp_options => { weight => n } } + +Instead of calling C pass C<< credit => $answer1 >> or +C<< credit => [$answer1, $answer2, ...] >> to the C method. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitEnddocumentMatter.pm b/lib/Perl/Critic/Policy/PG/ProhibitEnddocumentMatter.pm new file mode 100644 index 0000000000..0267989b31 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitEnddocumentMatter.pm @@ -0,0 +1,41 @@ +package Perl::Critic::Policy::PG::ProhibitEnddocumentMatter; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'There is content after the ENDDOCUMENT call'; +use constant EXPLANATION => 'Remove this content. The ENDDOCUMENT call should be at the end of the problem.'; +use constant SCORE => 5; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub default_maximum_violations_per_document ($) { return 1; } + +sub violates ($self, $element, $document) { + return $self->violation(DESCRIPTION, { score => SCORE, explanation => EXPLANATION }, $element) + if $element eq 'ENDDOCUMENT' + && is_function_call($element) + && ($document->{_doc}{untranslatedCode} // '') =~ /ENDDOCUMENT[^\n]*\n(.*)/s + && $1 =~ /\S/; + return; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitEnddocumentMatter - There should not be any +content after the C call in a problem. + +=head1 DESCRIPTION + +The C call is intended to signify the end of the problem code. +Although all content after the C call is ignored, there should not +be any content (text or code) in this area. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitGraphMacros.pm b/lib/Perl/Critic/Policy/PG/ProhibitGraphMacros.pm new file mode 100644 index 0000000000..437951b0d0 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitGraphMacros.pm @@ -0,0 +1,42 @@ +package Perl::Critic::Policy::PG::ProhibitGraphMacros; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); + +use constant DESCRIPTION => 'The init_graph function from PGgraphmacros.pl is called'; +use constant EXPLANATION => 'PGgraphmacros.pl generates poor quality graphics. Consider using a modern alternative.'; +use constant SCORE => 20; +use constant SAMPLE_PROBLEMS => [ + [ 'TikZ Graph Images' => 'ProblemTechniques/TikZImages' ], + [ 'Inserting Images in PGML' => 'ProblemTechniques/Images' ], + [ 'Function Plot' => 'Algebra/FunctionPlot' ] +]; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $) { + return unless $element eq 'init_graph' && is_function_call($element); + return $self->violation(DESCRIPTION, + { score => SCORE, explanation => EXPLANATION, sampleProblems => SAMPLE_PROBLEMS }, $element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitGraphMacros - L generates +poor quality graphics. Modern alternatives should be used instead. + +=head1 DESCRIPTION + +L generates poor quality graphics. Replace its usage with a +modern alternative such as L, L, or L. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitMultipleLoadMacrosCalls.pm b/lib/Perl/Critic/Policy/PG/ProhibitMultipleLoadMacrosCalls.pm new file mode 100644 index 0000000000..6dd18ca2e2 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitMultipleLoadMacrosCalls.pm @@ -0,0 +1,38 @@ +package Perl::Critic::Policy::PG::ProhibitMultipleLoadMacrosCalls; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'loadMacros is called multiple times'; +use constant EXPLANATION => 'Consolidate multiple loadMacros calls into a single call.'; +use constant SCORE => 20; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Document) } + +sub violates ($self, $element, $document) { + my $tokens = $document->find('PPI::Token'); + return unless $tokens; + my @loadMacrosCalls = grep { $_ eq 'loadMacros' && is_function_call($_) } @$tokens; + shift @loadMacrosCalls; + return map { $self->violation(DESCRIPTION, { score => SCORE, explanation => EXPLANATION }, $_) } @loadMacrosCalls; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitMultipleLoadMacrosCalls - The C +function should only be called once in each problem. + +=head1 DESCRIPTION + +The C function should only be called once in each problem. +Consolidate multiple C calls into a single call and make sure that +all macros that are loaded are actually used in the problem. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitOldText.pm b/lib/Perl/Critic/Policy/PG/ProhibitOldText.pm new file mode 100644 index 0000000000..b46ccbe8e1 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitOldText.pm @@ -0,0 +1,53 @@ +package Perl::Critic::Policy::PG::ProhibitOldText; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'A BEGIN_%1$s/END_%1$s block is used for problem text'; +use constant EXPLANATION => 'Load the macro PGML.pl and replace the BEGIN_%1$s/END_%1$s ' + . 'block with a BEGIN_PGML%2$s/END_PGML%2$s block.'; +use constant SCORE => 20; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::HereDoc) } + +sub violates ($self, $element, $document) { + if ($element->terminator =~ /^END_(TEXT|SOLUTION|HINT)?$/ + && $element->parent + && $element->parent->parent + && $element->parent->parent->parent + && $element->parent->parent->parent->first_element eq 'EV3P' + && is_function_call($element->parent->parent->parent->first_element) + && $element->parent->parent->parent->parent + && $element->parent->parent->parent->parent->parent + && $element->parent->parent->parent->parent->parent->first_element =~ /^(STATEMENT|HINT|SOLUTION)$/ + && is_function_call($element->parent->parent->parent->parent->parent->first_element)) + { + my $oldType = $1 eq 'STATEMENT' ? 'TEXT' : $1; + return $self->violation( + sprintf(DESCRIPTION, $oldType), + { score => SCORE, explanation => sprintf(EXPLANATION, $oldType, $1 eq 'STATEMENT' ? '' : "_$1") }, + $element->parent->parent->parent->parent->parent + ); + } + return; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitOldText - Replace old PG text usage with PGML. + +=head1 DESCRIPTION + +Load the C macro and replace all C/C, +C/C, and C/C blocks with +C/C, C/C, and +C/C blocks. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitUnnecessarilySettingShowPartialCorrectAnswers.pm b/lib/Perl/Critic/Policy/PG/ProhibitUnnecessarilySettingShowPartialCorrectAnswers.pm new file mode 100644 index 0000000000..2faac1d9ff --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitUnnecessarilySettingShowPartialCorrectAnswers.pm @@ -0,0 +1,42 @@ +package Perl::Critic::Policy::PG::ProhibitUnnecessarilySettingShowPartialCorrectAnswers; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); + +use constant DESCRIPTION => '$showPartialCorrectAnswers is set to 1'; +use constant EXPLANATION => 'The value of $showPartialCorrectAnswers is 1 by default, ' + . 'so it should only ever be set to 0 to change the value.'; +use constant SCORE => 5; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Operator) } + +sub violates ($self, $element, $) { + return unless is_assignment_operator($element); + my $left = $element->sprevious_sibling; + my $right = $element->snext_sibling; + return $self->violation(DESCRIPTION, { score => SCORE, explanation => EXPLANATION }, $left) + if $left && $left eq '$showPartialCorrectAnswers' && $element eq '=' && $right && $right eq '1'; + return; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitUnnecessarilySettingShowPartialCorrectAnswers +- There is no need to set C<$showPartialCorrectAnswers> to 1 since that is the +default value. + +=head1 DESCRIPTION + +The value of C<$showPartialCorrectAnswers> is 1 by default, so it should only +ever be set to 0 to change the value. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/RequireImageAltAttribute.pm b/lib/Perl/Critic/Policy/PG/RequireImageAltAttribute.pm new file mode 100644 index 0000000000..7fbc026577 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/RequireImageAltAttribute.pm @@ -0,0 +1,96 @@ +package Perl::Critic::Policy::PG::RequireImageAltAttribute; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(parsePGMLBlock parseTextBlock); + +use constant DESCRIPTION => 'An image is missing the alt attribute'; +use constant EXPLANATION => 'Add an alt attribute that describes the image content.'; +use constant SCORE => 10; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::HereDoc PPI::Token::Word) } + +sub hasAltTag ($self, $element) { + my @args = + map { $_->[0]->isa('PPI::Token::Quote') ? $_->[0]->string : $_->[0]->content } parse_arg_list($element); + shift @args; # Remove the image argument. + my %args = @args % 2 ? () : @args; + return $args{alt} || ($args{extra_html_tags} && $args{extra_html_tags} =~ /alt/); +} +use Mojo::Util qw(dumper); + +sub violates ($self, $element, $) { + my @violations; + if ($element->isa('PPI::Token::Word') && $element eq 'image' && is_function_call($element)) { + push(@violations, $self->violation(DESCRIPTION, { score => SCORE, explanation => EXPLANATION }, $element)) + unless $self->hasAltTag($element); + } elsif ( + $element->isa('PPI::Token::HereDoc') + && $element->terminator =~ /^END_(PGML|PGML_SOLUTION|PGML_HINT|TEXT|HINT|SOLUTION)?$/ + && $element->parent + && $element->parent->parent + && $element->parent->parent->parent + && ($element->parent->parent->parent->first_element eq 'PGML::Format2' + || $element->parent->parent->parent->first_element eq 'EV3P') + && is_function_call($element->parent->parent->parent->first_element) + && $element->parent->parent->parent->parent + && $element->parent->parent->parent->parent->parent + && $element->parent->parent->parent->parent->parent->first_element =~ /^(STATEMENT|HINT|SOLUTION)$/ + && is_function_call($element->parent->parent->parent->parent->parent->first_element) + ) + { + for my $command ( + @{ + ( + $element->terminator =~ /PGML/ + ? parsePGMLBlock($element->heredoc)->{commands} + : parseTextBlock($element->heredoc)->{commands} + ) // [] + } + ) + { + for (grep { $_ eq 'image' && is_function_call($_) } @{ $command->find('PPI::Token::Word') || [] }) { + next if $self->hasAltTag($_); + push( + @violations, + $self->violation( + DESCRIPTION + . ' inside the ' + . ($element->terminator =~ s/END/BEGIN/r) . '/' + . ($element->terminator) + . ' block', + { score => SCORE, explanation => EXPLANATION }, + $element->parent->parent->parent->parent->parent + ) + ); + } + } + } + + return @violations; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::RequireImageAltAttribute - Images created with the +C method should have the C attribute set. + +=head1 DESCRIPTION + +The C attribute is crucial for accessibility, especially for visually +impaired users who rely on screen readers to understand the content of the +problem. So all images added to a problem should have the C attribute set. +Note that it can be set to the empty string to indicate that the image is not +essential to the meaning of the problem content. Generally it is better to use +the PGML syntax for images C<[!alternate text!]{$image}> rather than using the +C method. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/RequireMetadata.pm b/lib/Perl/Critic/Policy/PG/RequireMetadata.pm new file mode 100644 index 0000000000..ab081f9dfa --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/RequireMetadata.pm @@ -0,0 +1,51 @@ +package Perl::Critic::Policy::PG::RequireMetadata; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'The %s metadata tag is required'; +use constant EXPLANATION => 'Include the required metadata tags at the beginning of the problem file.'; +use constant SCORE => 5; +use constant REQUIRED_METADATA => [ 'DBsubject', 'DBchapter', 'DBsection', 'KEYWORDS' ]; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Document) } + +sub violates ($self, $element, $document) { + my $comments = $document->find('PPI::Token::Comment'); + my %foundMetadata; + if ($comments) { + for my $comment (@$comments) { + my ($metadataType) = grep { $comment =~ /#\s*$_\(/i } @{ REQUIRED_METADATA() }; + $foundMetadata{$metadataType} = 1 if $metadataType; + } + } + + my @violations; + for (@{ REQUIRED_METADATA() }) { + push(@violations, + $self->violation(sprintf(DESCRIPTION, $_), { score => SCORE, explanation => EXPLANATION }, $document)) + unless $foundMetadata{$_}; + } + return @violations; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::RequireMetadata - All problems should have the +appropriate OPL metadata tags set. + +=head1 DESCRIPTION + +All problems should have the appropriate OPL metadata tags set. The required +metadata attributes should be set at the beginning of the problem file before +the C call. The metadata tags that are required for all problems are +C, C, C, and C. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/RequireSolution.pm b/lib/Perl/Critic/Policy/PG/RequireSolution.pm new file mode 100644 index 0000000000..eb14e1a647 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/RequireSolution.pm @@ -0,0 +1,61 @@ +package Perl::Critic::Policy::PG::RequireSolution; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'A solution is not included in this problem'; +use constant EXPLANATION => 'A solution should be included in all problems.'; +use constant SCORE => 25; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Document) } + +sub default_maximum_violations_per_document ($) { return 1; } + +sub violates ($self, $element, $document) { + my $solutionFound = 0; + if (my $heredocs = $document->find('PPI::Token::HereDoc')) { + for (@$heredocs) { + if ( + $_->terminator =~ /^END_(PGML_)?SOLUTION$/ + && $_->parent + && $_->parent->parent + && $_->parent->parent->parent + && ($_->parent->parent->parent->first_element eq 'PGML::Format2' + || $_->parent->parent->parent->first_element eq 'EV3P') + && is_function_call($_->parent->parent->parent->first_element) + && $_->parent->parent->parent->parent + && $_->parent->parent->parent->parent->parent + && $_->parent->parent->parent->parent->parent->first_element eq 'SOLUTION' + && is_function_call($_->parent->parent->parent->first_element) + ) + { + $solutionFound = 1; + last; + } + } + } + return $self->violation(DESCRIPTION, { score => SCORE, explanation => EXPLANATION }, $document) + unless $solutionFound; + return; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::RequireSolution - All problems should provide a +solution. + +=head1 DESCRIPTION + +A solution should be included in all problems. Note that a solution should +demonstrate all steps to solve a problem, and should certainly not just give the +answers to questions in the problem. This is one of the most challenging parts +of PG problem authoring, but the solution should not be omitted. + +=cut diff --git a/lib/Plots/Axes.pm b/lib/Plots/Axes.pm index f1da7cb454..e8e4986f94 100644 --- a/lib/Plots/Axes.pm +++ b/lib/Plots/Axes.pm @@ -63,7 +63,7 @@ It is also possible to get multiple options for both axes using the get method, a reference to a hash of requested keys, such as: $bounds = $plot->axes->get('xmin', 'xmax', 'ymin', 'ymax'); - # The following is equivlant to $plot->axes->grid + # The following is equivalent to $plot->axes->grid $grid = $plot->axes->get('xmajor', 'xminor', 'xtick_delta', 'ymajor', 'yminor', 'ytick_delta'); It is also possible to get the bounds as an array in the order xmin, ymin, xmax, ymax @@ -87,19 +87,89 @@ The maximum value the axis shows. Default is 5. This is the number of major tick marks to include on the axis. This number is used to compute the C as the difference between the C and C values -and the number of ticks. Default: 5. +and the number of ticks. Note that this is only used if C is zero +and C is undefined. Default: 5. =item tick_delta -This is the distance between each major tick mark, starting from the origin. -If this is set to 0, this distance is set by using the number of ticks, C. -Default is 0. +This is the distance between each major tick mark, starting from the origin. If +this is set to 0 and C is not 0, then this distance is computed +to be the product of the C and the C, and if this is +set to 0 and C is undefined then this is computed to be the +difference between the C and C divided by the C. Default: 0 =item tick_labels -This can be either 1 (show) or 0 (don't show) the labels for the major ticks. +This can be set to 1 to show the labels for the major ticks, 0 to not show the +labels for the major ticks, or to a reference to a hash whose keys are tick +positions, and whose values are tick labels to be shown at those positions. Default: 1 +The following is an example of passing a reference to a hash for this option. + + tick_labels => { 5 => '\(a\)' } + +In this case if there is a major tick at 5, then the label that will be shown +there is 'a' and the label will be rendered via MathJax. Note that if there is +not a major tick at 5, then the label will be unused. At any other major tick +that is shown, the position will be shown for the label and will be formatted +according to the C option. + +This option is most useful in combination with the C option +below. With that option the precise list of major ticks to be shown can be +specified, and the labels for those ticks specified with this option. For +example, + + tick_positions => [2, 4], + tick_labels => { 2 => 'a', 4 => 'b' }, + +would place a tick at 2 labeled 'a', and a tick at 4 labeled 'b'. No other ticks +would be shown on the axis. + +Note that if the hash reference value is used, the C does not +apply. You are responsible for formatting the labels as you would like them to +appear. If you want the labels rendered via MathJax, then wrap the labels in +C<\(> and C<\)>. + +=item tick_label_format + +This can be one of "decimal", "fraction", "multiple", or "scinot". If this is +"decimal", then tick labels will be displayed in decimal format. If this is +"fraction", then tick labels will be displayed as (improper) fractions. If this +is "mixed", then tick labels will be displayed as mixed numbers. If this is +"scinot", then tick labels will be displayed in scientific notation. Default: +"decimal" + +=item tick_label_digits + +The number of decimal places to round tick labels to when the +C is "decimal" or "scinot". Default: 2 + +=item tick_distance + +This is the unscaled distance between each major tick mark starting from the +origin when the axis is scaled by the C factor. If this is 0, then +this will be computed to be the C divided by the C. +Default: 0 + +=item tick_scale + +This is used in combination with the C above to calculate the +C. Default: 1 + +=item tick_scale_symbol + +This is appended to major tick labels. Default: '' + +=item tick_positions + +Set this to a reference to an array of values to be used for the positions of +ticks that will be shown on the axis. In this case the C, +C, and C options will not be used to generate tick +positions. If this is set to 0 (or is not an array reference), then the tick +positions will be computed using the values of the C, +C, and C options. Default: 0 + =item show_ticks This can be either 1 (show) or 0 (don't show) the tick lines. If ticks are @@ -115,8 +185,13 @@ Show (1) or don't show (0) grid lines at the tick marks. Default is 1. =item minor -This sets the number of minor grid lines per major grid line. If this is -set to 0, no minor grid lines are shown. Default is 3. +This sets the number of minor ticks (and minor grid lines if minor_grids is 1) +per major tick. If this is set to 0, no minor ticks are shown. Default: 3 + +=item minor_grids + +If this is 1, then grid lines are shown at minor ticks, and if this is 0, then +grid lines are not shown at minor ticks. Default: 1 =item visible @@ -139,9 +214,23 @@ Default 'middle' or 'center'. The position in terms of the appropriate variable to draw the axis if the location is set to 'middle' or 'center'. Default is 0. +=item arrows_both + +Configures if arrows should be drawn in both directions (1) or only in the +positive direction (0) at the axis ends. In other words, this is a choice +between the convention that arrows are meant to indicate that the axis lines +continue forever, or the convention that arrows are meant to indicate the +positive direction of the axis only. Default: 0 + =item jsx_options -A hash reference of options to be passed to the JSXGraph axis objects. +A hash reference of options to be passed to the JSXGraph axis object. + +=item jsx_grid_options + +A hash reference of options to be passed to the JSXGraph grid object. Note that +the grid is implemented as an axis with ticks the extend to infinity. So the +options are really JSXGraph axis options. =back @@ -185,6 +274,21 @@ Configures if the Tikz axis should be drawn on top of the graph (1) or below the Useful when filling a region that covers an axis, if the axis are on top they will still be visible after the fill, otherwise the fill will cover the axis. Default: 0 +Note that this setting is not honored for the JSXGraph image type. + +This is not the best way of ensuring that axis elements are not covered by a +fill. If this is used, then not only is the fill region placed behind the axis +and the grid, but all graphed elements are behind the axis and the grid which is +usually not desirable. A better way is to use the "axis background" C to +only place the fill on the "axis background" layer, and leave everything else on +top of the axis. + +=item mathjax_tick_labels + +If this is 1, then tick labels will be displayed using MathJax. If this is 0, +then ticks will be displayed as basic text. This only applies to the JSXGraph +output type. Default: 1 + =item jsx_navigation Either allow (1) or don't allow (0) the user to pan and zoom the view port of the @@ -196,6 +300,13 @@ of the graph that can be zoomed in or out. Default: 0 A hash reference of options to be passed to the JSXGraph board object. +=item tikz_options + +Additional options to be passed to the pgfplots axis definition. This should be +a single string. For example, to make longer and thicker x axis ticks use + + tikz_options => 'x tick style={line width=2pt},major tick length=0.6cm' + =back =cut @@ -206,18 +317,20 @@ use strict; use warnings; sub new { - my $class = shift; - my $self = bless { + my ($class, @options) = @_; + my $self = bless { xaxis => {}, yaxis => {}, styles => { - aria_label => 'Graph', - aria_description => 'Generated graph', - grid_color => 'gray', - grid_alpha => 40, - show_grid => 1, + aria_label => 'Graph', + aria_description => 'Generated graph', + grid_color => 'gray', + grid_alpha => 40, + show_grid => 1, + axis_on_top => 0, + mathjax_tick_labels => 1, }, - @_ + @options }, $class; $self->xaxis($self->axis_defaults('x')); @@ -228,18 +341,26 @@ sub new { sub axis_defaults { my ($self, $axis) = @_; return ( - visible => 1, - min => -5, - max => 5, - label => $axis eq 'y' ? '\(y\)' : '\(x\)', - location => $axis eq 'y' ? 'center' : 'middle', - position => 0, - tick_labels => 1, - show_ticks => 1, - tick_delta => 0, - tick_num => 5, - major => 1, - minor => 3, + visible => 1, + min => -5, + max => 5, + label => $axis eq 'y' ? '\(y\)' : '\(x\)', + location => $axis eq 'y' ? 'center' : 'middle', + position => 0, + tick_labels => 1, + tick_label_format => 'decimal', + tick_label_digits => 2, + tick_distance => 0, + tick_scale => 1, + tick_scale_symbol => '', + tick_positions => 0, + show_ticks => 1, + tick_delta => 0, + tick_num => 5, + major => 1, + minor => 3, + minor_grids => 1, + arrows_both => 0, ); } @@ -256,8 +377,11 @@ sub axis { map { $self->{$axis}{$_} = $item->{$_}; } (keys %$item); return; } - # Deal with ticks individually since they may need to be generated. - return $item eq 'tick_delta' ? $self->tick_delta($self->{$axis}) : $self->{$axis}{$item}; + # Deal with the tick_delta and tick_distance individually since they may need to be computed. + return + $item eq 'tick_delta' ? $self->tick_delta($self->{$axis}) + : $item eq 'tick_distance' ? $self->tick_distance($self->{$axis}) + : $self->{$axis}{$item}; } sub xaxis { @@ -322,14 +446,28 @@ sub style { sub tick_delta { my ($self, $axis) = @_; return $axis->{tick_delta} if $axis->{tick_delta}; - return 2 unless $axis->{tick_num}; - $axis->{tick_delta} = ($axis->{max} - $axis->{min}) / $axis->{tick_num} if $axis->{tick_num}; + if ($axis->{tick_distance}) { + $axis->{tick_delta} = $axis->{tick_distance} * ($axis->{tick_scale} || 1); + } elsif ($axis->{tick_num}) { + $axis->{tick_delta} = ($axis->{max} - $axis->{min}) / $axis->{tick_num}; + } else { + $axis->{tick_delta} = 2; + } return $axis->{tick_delta}; } +sub tick_distance { + my ($self, $axis) = @_; + return $axis->{tick_distance} if $axis->{tick_distance}; + my $tick_delta = $self->tick_delta($axis); + $axis->{tick_distance} = $axis->{tick_delta} / ($axis->{tick_scale} || 1); + return $axis->{tick_distance}; +} + sub grid { my $self = shift; - return $self->get('xmajor', 'xminor', 'xtick_delta', 'ymajor', 'yminor', 'ytick_delta'); + return $self->get('xmajor', 'xminor_grids', 'xminor', 'xtick_delta', 'ymajor', 'yminor_grids', 'yminor', + 'ytick_delta'); } sub bounds { diff --git a/lib/Plots/Data.pm b/lib/Plots/Data.pm index 35c10f7f24..d40afdc50d 100644 --- a/lib/Plots/Data.pm +++ b/lib/Plots/Data.pm @@ -66,7 +66,7 @@ stored in the C<< $data->{function} >> hash, though other data is stored as a st ); Note, the first argument must be $self->context when called from C -to use a single context for all C objects. +to use a single context for all C objects. This is also used to set a two variable function (used for slope or vector fields): @@ -116,7 +116,7 @@ Takes a MathObject C<$formula> and replaces the function with either a JavaScript or PGF function string. If the function contains any function tokens not supported, a warning and empty string is returned. - $formula The mathobject formula object, either $self->{function}{Fx} or $self->{function}{Fy}. + $formula The MathObject formula object, either $self->{function}{Fx} or $self->{function}{Fy}. $type 'js' or 'PGF' (falls back to js for any input except 'PGF'). $xvar The x-variable name, $self->{function}{xvar}. $yvar The y-variable name, $self->{function}{yvar}, for vector fields. @@ -217,13 +217,11 @@ sub set_function { }; for my $key ('Fx', 'Fy', 'xvar', 'yvar', 'xmin', 'xmax', 'ymin', 'ymax', 'xsteps', 'ysteps') { next unless defined $options{$key}; - $f->{$key} = $options{$key}; - delete $options{$key}; + $f->{$key} = delete $options{$key}; } for my $key ('var', 'min', 'max', 'steps') { next unless defined $options{$key}; - $f->{"x$key"} = $options{$key}; - delete $options{$key}; + $f->{"x$key"} = delete $options{$key}; } return unless $f->{Fy} ne ''; @@ -257,13 +255,15 @@ sub function_string { # Ensure -x^2 gets print as -(x^2), since JavaScript finds this ambiguous. my $extraParens = $formula->context->flag('showExtraParens'); - my $format = $formula->context->{format}{number}; - $formula->context->flags->set(showExtraParens => 2); + # Ensure that abs(x) is stringified as abs(x) instead of |x|. + my $stringifyAbsAsFunction = $formula->context->flag('stringifyAbsAsFunction'); + my $format = $formula->context->{format}{number}; + $formula->context->flags->set(showExtraParens => 2, stringifyAbsAsFunction => 1); $formula->context->{format}{number} = "%f#"; # Get no bracket string for $formula my $func = $formula . ""; $func =~ s/\s//g; - $formula->context->flags->set(showExtraParens => $extraParens); + $formula->context->flags->set(showExtraParens => $extraParens, stringifyAbsAsFunction => $stringifyAbsAsFunction); $formula->context->{format}{number} = $format; my %tokens; @@ -318,8 +318,8 @@ sub function_string { ceil => 'Math.ceil', sign => 'Math.sign', int => 'Math.trunc', - log => 'Math.ln', - ln => 'Math.ln', + log => 'Math.log', + ln => 'Math.log', cos => 'Math.cos', sin => 'Math.sin', tan => 'Math.tan', @@ -334,11 +334,11 @@ sub function_string { sinh => 'Math.sinh', tanh => 'Math.tanh', acosh => 'Math.acosh', - arccosh => 'Math.arccosh', + arccosh => 'Math.acosh', asinh => 'Math.asinh', arcsinh => 'Math.asinh', atanh => 'Math.atanh', - arctanh => 'Math.arctanh', + arctanh => 'Math.atanh', min => 'Math.min', max => 'Math.max', random => 'Math.random', diff --git a/lib/Plots/GD.pm b/lib/Plots/GD.pm deleted file mode 100644 index 5b75c34640..0000000000 --- a/lib/Plots/GD.pm +++ /dev/null @@ -1,362 +0,0 @@ - -=head1 DESCRIPTION - -This is the code that takes a C and creates the GD code for generation. - -See L for more details. - -=cut - -package Plots::GD; - -use GD; - -use strict; -use warnings; - -sub new { - my ($class, $plots) = @_; - return bless { - image => '', - plots => $plots, - position => [ 0, 0 ], - colors => {}, - image => GD::Image->new($plots->size) - }, $class; -} - -sub plots { - my $self = shift; - return $self->{plots}; -} - -sub im { - my $self = shift; - return $self->{image}; -} - -sub position { - my ($self, $x, $y) = @_; - return wantarray ? @{ $self->{position} } : $self->{position} unless (defined($x) && defined($y)); - $self->{position} = [ $x, $y ]; - return; -} - -sub color { - my ($self, $color) = @_; - $self->{colors}{$color} = $self->im->colorAllocate(@{ $self->plots->colors($color) }) - unless $self->{colors}{$color}; - return $self->{colors}{$color}; -} - -# Translate x and y coordinates to pixels on the graph. -sub im_x { - my ($self, $x) = @_; - return unless defined($x); - my $plots = $self->plots; - my ($xmin, $xmax) = ($plots->axes->xaxis('min'), $plots->axes->xaxis('max')); - return int(($x - $xmin) * $plots->{width} / ($xmax - $xmin)); -} - -sub im_y { - my ($self, $y) = @_; - return unless defined($y); - my $plots = $self->plots; - my ($ymin, $ymax) = ($plots->axes->yaxis('min'), $plots->axes->yaxis('max')); - return int(($ymax - $y) * $plots->{height} / ($ymax - $ymin)); -} - -sub moveTo { - my ($self, $x, $y) = @_; - $x = $self->im_x($x); - $y = $self->im_y($y); - $self->position($x, $y); - return; -} - -sub lineTo { - my ($self, $x, $y, $color, $width, $dashed) = @_; - $color = 'default_color' unless defined($color); - $color = $self->color($color); - $width = 1 unless defined($width); - $dashed = 0 unless defined($dashed); - $x = $self->im_x($x); - $y = $self->im_y($y); - - $self->im->setThickness($width); - if ($dashed =~ /dash/) { - my @dashing = ($color) x (4 * $width * $width); - my @spacing = (GD::gdTransparent) x (3 * $width * $width); - $self->im->setStyle(@dashing, @spacing); - $self->im->line($self->position, $x, $y, GD::gdStyled); - } elsif ($dashed =~ /dot/) { - my @dashing = ($color) x (1 * $width * $width); - my @spacing = (GD::gdTransparent) x (2 * $width * $width); - $self->im->setStyle(@dashing, @spacing); - $self->im->line($self->position, $x, $y, GD::gdStyled); - } else { - $self->im->line($self->position, $x, $y, $color); - } - $self->im->setThickness(1); - $self->position($x, $y); - return; -} - -# Draw functions / lines / arrows -sub draw_data { - my ($self, $pass) = @_; - my $plots = $self->plots; - $pass = 0 unless $pass; - for my $data ($plots->data('function', 'dataset')) { - $data->gen_data; - my $n = $data->size - 1; - my $x = $data->x; - my $y = $data->y; - my $color = $data->style('color'); - my $width = $data->style('width'); - $self->moveTo($x->[0], $y->[0]); - for (1 .. $n) { - $self->lineTo($x->[$_], $y->[$_], $color, $width, $data->style('linestyle')); - } - - if ($pass == 2) { - my $r = int(3 + $width); - my $start = $data->style('start_mark') || 'none'; - if ($start eq 'circle' || $start eq 'closed_circle') { - $self->draw_circle_stamp($data->x(0), $data->y(0), $r, $color, 1); - } elsif ($start eq 'open_circle') { - $self->draw_circle_stamp($data->x(0), $data->y(0), $r, $color); - } elsif ($start eq 'arrow') { - $self->draw_arrow_head($data->x(1), $data->y(1), $data->x(0), $data->y(0), $color, $width); - } - - my $end = $data->style('end_mark') || 'none'; - if ($end eq 'circle' || $end eq 'closed_circle') { - $self->draw_circle_stamp($data->x($n), $data->y($n), $r, $color, 1); - } elsif ($end eq 'open_circle') { - $self->draw_circle_stamp($data->x($n), $data->y($n), $r, $color); - } elsif ($end eq 'arrow') { - $self->draw_arrow_head($data->x($n - 1), $data->y($n - 1), $data->x($n), $data->y($n), $color, $width); - } - } - } - return; -} - -# Label helpers -sub get_gd_font { - my ($self, $font) = @_; - if ($font eq 'tiny') { return GD::gdTinyFont; } - elsif ($font eq 'small') { return GD::gdSmallFont; } - elsif ($font eq 'large') { return GD::gdLargeFont; } - elsif ($font eq 'giant') { return GD::gdGiantFont; } - return GD::gdMediumBoldFont; -} - -sub label_offset { - my ($self, $loc, $str, $fontsize) = @_; - my $offset = 0; - # Add an additional 2px offset for the edges 'right', 'bottom', 'left', and 'top'. - if ($loc eq 'right') { $offset -= length($str) * $fontsize + 2; } - elsif ($loc eq 'bottom') { $offset -= $fontsize + 2; } - elsif ($loc eq 'center') { $offset -= length($str) * $fontsize / 2; } - elsif ($loc eq 'middle') { $offset -= $fontsize / 2; } - else { $offset = 2; } # Both 'left' and 'top'. - return $offset; -} - -sub draw_label { - my ($self, $str, $x, $y, %options) = @_; - my $font = $self->get_gd_font($options{fontsize} || 'medium'); - my $color = $self->color($options{color} || 'default_color'); - my $xoff = $self->label_offset($options{h_align} || 'center', $str, $font->width); - my $yoff = $self->label_offset($options{v_align} || 'middle', $str, $font->height); - - if ($options{orientation} && $options{orientation} eq 'vertical') { - $self->im->stringUp($font, $self->im_x($x) + $xoff, $self->im_y($y) + $yoff, $str, $color); - } else { - $self->im->string($font, $self->im_x($x) + $xoff, $self->im_y($y) + $yoff, $str, $color); - } - return; -} - -sub draw_arrow_head { - my ($self, $x1, $y1, $x2, $y2, $color, $w) = @_; - return unless @_ > 4; - $color = $self->color($color || 'default_color'); - $w = 1 unless $w; - ($x1, $y1) = ($self->im_x($x1), $self->im_y($y1)); - ($x2, $y2) = ($self->im_x($x2), $self->im_y($y2)); - - my $dx = $x2 - $x1; - my $dy = $y2 - $y1; - my $len = sqrt($dx * $dx + $dy * $dy); - my $ux = $dx / $len; # Unit vector in direction of arrow. - my $uy = $dy / $len; - my $px = -1 * $uy; # Unit vector perpendicular to arrow. - my $py = $ux; - my $hbx = $x2 - 7 * $w * $ux; - my $hby = $y2 - 7 * $w * $uy; - my $head = GD::Polygon->new; - $head->addPt($x2, $y2); - $head->addPt($hbx + 3 * $w * $px, $hby + 3 * $w * $py); - $head->addPt($hbx - 3 * $w * $px, $hby - 3 * $w * $py); - $self->im->setThickness($w); - $self->im->filledPolygon($head, $color); - $self->im->setThickness(1); - return; -} - -sub draw_circle_stamp { - my ($self, $x, $y, $r, $color, $filled) = @_; - my $d = $r ? 2 * $r : 8; - $color = $self->color($color || 'default_color'); - $self->im->filledArc($self->im_x($x), $self->im_y($y), $d, $d, 0, 360, $self->color('white')); - $self->im->filledArc($self->im_x($x), $self->im_y($y), $d, $d, 0, 360, $color, $filled ? () : GD::gdNoFill); - return; -} - -sub draw { - my $self = shift; - my $plots = $self->plots; - my $axes = $plots->axes; - my $grid = $axes->grid; - my $width = $plots->{width}; - my $height = $plots->{height}; - - # Initialize image - $self->im->interlaced('true'); - $self->im->fill(1, 1, $self->color('white')); - - # Plot data first, then fill in regions before adding axes, grid, etc. - $self->draw_data(1); - - # Fill regions - for my $region ($plots->data('fill_region')) { - $self->im->fill($self->im_x($region->x(0)), $self->im_y($region->y(0)), $self->color($region->style('color'))); - } - - # Gridlines - my ($xmin, $ymin, $xmax, $ymax) = $axes->bounds; - my $grid_color = $axes->style('grid_color'); - my $grid_style = $axes->style('grid_style'); - my $show_grid = $axes->style('show_grid'); - if ($show_grid && $grid->{xmajor}) { - my $xminor = $grid->{xminor} || 0; - my $dx = $grid->{xtick_delta} || 1; - my $x = (int($xmax / $dx) + 1) * $dx; - my $end = (int($xmin / $dx) - 1) * $dx; - while ($x >= $end) { - $self->moveTo($x, $ymin); - $self->lineTo($x, $ymax, $grid_color, 0.5, 1); - for (0 .. $xminor) { - my $tmp_x = $x + $_ * $dx / ($xminor + 1); - $self->moveTo($tmp_x, $ymin); - $self->lineTo($tmp_x, $ymax, $grid_color, 0.5, 1); - } - $x -= $dx; - } - } - if ($show_grid && $grid->{ymajor}) { - my $yminor = $grid->{yminor} || 0; - my $dy = $grid->{ytick_delta} || 1; - my $y = (int($ymax / $dy) + 1) * $dy; - my $end = (int($ymin / $dy) - 1) * $dy; - while ($y >= $end) { - $self->moveTo($xmin, $y); - $self->lineTo($xmax, $y, $grid_color, 0.5, 1); - for (0 .. $yminor) { - my $tmp_y = $y + $_ * $dy / ($yminor + 1); - $self->moveTo($xmin, $tmp_y); - $self->lineTo($xmax, $tmp_y, $grid_color, 0.5, 1); - } - $y -= $dy; - } - } - - # Plot axes - my $xloc = $axes->xaxis('location') || 'middle'; - my $yloc = $axes->yaxis('location') || 'center'; - my $xpos = ($yloc eq 'box' || $yloc eq 'left') ? $xmin : $yloc eq 'right' ? $xmax : $axes->yaxis('position'); - my $ypos = ($xloc eq 'box' || $xloc eq 'bottom') ? $ymin : $xloc eq 'top' ? $ymax : $axes->xaxis('position'); - $xpos = $xmin if $xpos < $xmin; - $xpos = $xmax if $xpos > $xmax; - $ypos = $ymin if $ypos < $ymin; - $ypos = $ymax if $ypos > $ymax; - - if ($axes->xaxis('visible')) { - my $xlabel = $axes->xaxis('label') =~ s/\\[\(\[\)\]]//gr; - my $tick_align = ($self->im_y($ymin) - $self->im_y($ypos) < 5) ? 'bottom' : 'top'; - my $label_align = ($self->im_y($ypos) - $self->im_y($ymax) < 5) ? 'top' : 'bottom'; - my $label_loc = $yloc eq 'right' && ($xloc eq 'top' || $xloc eq 'bottom') ? $xmin : $xmax; - - $self->moveTo($xmin, $ypos); - $self->lineTo($xmax, $ypos, 'black', 1.5, 0); - $self->draw_label( - $xlabel, $label_loc, $ypos, - fontsize => 'large', - v_align => $label_align, - h_align => $label_loc == $xmin ? 'left' : 'right' - ); - my $dx = $grid->{xtick_delta} || 1; - my $x = int($xmax / $dx) * $dx; - my $end = int($xmin / $dx) * $dx; - - while ($x >= $end) { - $self->draw_label($x, $x, $ypos, font => 'large', v_align => $tick_align, h_align => 'center') - unless $x == $xpos && $axes->yaxis('visible'); - $x -= $dx; - } - } - if ($axes->yaxis('visible')) { - my $ylabel = $axes->yaxis('label') =~ s/\\[\(\[\)\]]//gr; - my $tick_align = ($self->im_x($xpos) - $self->im_x($xmin) < 5) ? 'left' : 'right'; - my $label_align = ($self->im_x($xmax) - $self->im_x($xpos) < 5) ? 'right' : 'left'; - my $label_loc = ($yloc eq 'left' && $xloc eq 'top') || ($yloc eq 'right' && $xloc eq 'top') ? $ymin : $ymax; - - $self->moveTo($xpos, $ymin); - $self->lineTo($xpos, $ymax, 'black', 1.5, 0); - $self->draw_label( - $ylabel, $xpos, $label_loc, - fontsize => 'large', - v_align => $label_loc == $ymin ? 'bottom' : 'top', - h_align => $label_align - ); - - my $dy = $grid->{ytick_delta} || 1; - my $y = int($ymax / $dy) * $dy; - my $end = int($ymin / $dy) * $dy; - while ($y >= $end) { - $self->draw_label($y, $xpos, $y, font => 'large', v_align => 'middle', h_align => $tick_align) - unless $y == $ypos && $axes->xaxis('visible'); - $y -= $dy; - } - } - - # Draw data a second time to cleanup any issues with the grid and axes. - $self->draw_data(2); - - # Print Labels - for my $label ($plots->data('label')) { - $self->draw_label($label->style('label'), $label->x(0), $label->y(0), %{ $label->style }); - } - - # Draw stamps - for my $stamp ($plots->data('stamp')) { - my $symbol = $stamp->style('symbol'); - my $color = $stamp->style('color'); - my $r = $stamp->style('radius') || 4; - if ($symbol eq 'circle' || $symbol eq 'closed_circle') { - $self->draw_circle_stamp($stamp->x(0), $stamp->y(0), $r, $color, 1); - } elsif ($symbol eq 'open_circle') { - $self->draw_circle_stamp($stamp->x(0), $stamp->y(0), $r, $color); - } - } - - # Put a black frame around the picture - $self->im->rectangle(0, 0, $width - 1, $height - 1, $self->color('black')); - - return $plots->ext eq 'gif' ? $self->im->gif : $self->im->png; -} - -1; diff --git a/lib/Plots/JSXGraph.pm b/lib/Plots/JSXGraph.pm index f4d386360f..7c8f09db29 100644 --- a/lib/Plots/JSXGraph.pm +++ b/lib/Plots/JSXGraph.pm @@ -17,9 +17,10 @@ sub new { $plots->add_css_file('node_modules/jsxgraph/distrib/jsxgraph.css'); $plots->add_css_file('js/Plots/plots.css'); - $plots->add_js_file('node_modules/jsxgraph/distrib/jsxgraphcore.js'); + $plots->add_js_file('node_modules/jsxgraph/distrib/jsxgraphcore.js', { defer => undef }); + $plots->add_js_file('js/Plots/plots.js', { defer => undef }); - return bless { plots => $plots }, $class; + return bless { plots => $plots, names => { xaxis => 1 } }, $class; } sub plots { @@ -29,57 +30,138 @@ sub plots { sub HTML { my $self = shift; - my $name = $self->{name}; - my ($width, $height) = $self->plots->size; - - my $imageviewClass = $self->plots->axes->style('jsx_navigation') ? '' : ' image-view-elt'; - my $tabindex = $self->plots->axes->style('jsx_navigation') ? '' : ' tabindex="0"'; - my $details = $self->plots->{description_details} =~ s/LONG-DESCRIPTION-ID/${name}_details/r; - my $aria_details = $details ? qq! aria-details="${name}_details"! : ''; - my $divs = qq!
    plots; + my ($width, $height) = $plots->size; + + my $imageviewClass = $plots->axes->style('jsx_navigation') ? '' : ' image-view-elt'; + my $tabindex = $plots->axes->style('jsx_navigation') ? '' : ' tabindex="0"'; + my $roundedCornersClass = $plots->{rounded_corners} ? ' plots-jsxgraph-rounded' : ''; + my $details = $plots->{description_details} =~ s/LONG-DESCRIPTION-ID/$self->{name}_details/r; + my $aria_details = $details ? qq! aria-details="$self->{name}_details"! : ''; + + my $divs = + qq!
    !; - $divs = qq!
    $divs$details
    ! if ($details); + $divs = qq!
    $divs$details
    ! if $details; + + my $axes = $plots->axes; + my $xaxis_loc = $axes->xaxis('location'); + my $yaxis_loc = $axes->yaxis('location'); + my $xaxis_pos = $axes->xaxis('position'); + my $yaxis_pos = $axes->yaxis('position'); + my $show_grid = $axes->style('show_grid'); + my $grid = $axes->grid; + my ($xmin, $ymin, $xmax, $ymax) = $axes->bounds; + + my ($xvisible, $yvisible) = ($axes->xaxis('visible'), $axes->yaxis('visible')); + + my $options = {}; + + $options->{ariaDescription} = $axes->style('aria_description') if defined $axes->style('aria_description'); + + $options->{board}{title} = $axes->style('aria_label'); + $options->{board}{showNavigation} = $axes->style('jsx_navigation') ? 1 : 0; + $options->{board}{overrideOptions} = $axes->style('jsx_options') if $axes->style('jsx_options'); + + # Set the bounding box. Add padding for the axes at the edge of graph if needed. Note that the padding set here is + # not the final padding used in the end result. The plots.js JavaScript adjusts the padding to fit the axis label + # content. This just needs to add enough padding so that the label content has enough room to render, and so that + # the JavaScript knows where the adjustments are needed. + $options->{board}{boundingBox} = [ + $xmin - ( + $yvisible + && ($yaxis_loc eq 'left' || $yaxis_loc eq 'box' || $xmin == $yaxis_pos) ? 0.11 * ($xmax - $xmin) : 0 + ), + $ymax + ($xvisible && ($xaxis_loc eq 'top' || $ymax == $xaxis_pos) ? 0.11 * ($ymax - $ymin) : 0), + $xmax + ($yvisible && ($yaxis_loc eq 'right' || $xmax == $yaxis_pos) ? 0.11 * ($xmax - $xmin) : 0), + $ymin - ( + $xvisible + && ($xaxis_loc eq 'bottom' || $xaxis_loc eq 'box' || $ymin == $xaxis_pos) ? 0.11 * ($ymax - $ymin) : 0 + ) + ]; + + $options->{xAxis}{visible} = $xvisible; + ($options->{xAxis}{min}, $options->{xAxis}{max}) = ($xmin, $xmax); + if ($xvisible || ($show_grid && $grid->{xmajor})) { + $options->{xAxis}{position} = $xaxis_pos; + $options->{xAxis}{location} = $xaxis_loc; + $options->{xAxis}{ticks}{scale} = $axes->xaxis('tick_scale'); + $options->{xAxis}{ticks}{distance} = $axes->xaxis('tick_distance'); + $options->{xAxis}{ticks}{minorTicks} = $grid->{xminor}; + + my $xticks = $axes->xaxis('tick_positions'); + $options->{xAxis}{ticks}{positions} = $xticks if ref $xticks eq 'ARRAY'; + } + + $options->{yAxis}{visible} = $yvisible; + ($options->{yAxis}{min}, $options->{yAxis}{max}) = ($ymin, $ymax); + if ($yvisible || ($show_grid && $grid->{ymajor})) { + $options->{yAxis}{position} = $yaxis_pos; + $options->{yAxis}{location} = $yaxis_loc; + $options->{yAxis}{ticks}{scale} = $axes->yaxis('tick_scale'); + $options->{yAxis}{ticks}{distance} = $axes->yaxis('tick_distance'); + $options->{yAxis}{ticks}{minorTicks} = $grid->{yminor}; + + my $yticks = $axes->yaxis('tick_positions'); + $options->{yAxis}{ticks}{positions} = $yticks if ref $yticks eq 'ARRAY'; + } + + if ($show_grid) { + if ($grid->{xmajor} || $grid->{ymajor}) { + $options->{grid}{color} = $self->get_color($axes->style('grid_color')); + $options->{grid}{opacity} = $axes->style('grid_alpha') / 200; + } + + if ($grid->{xmajor}) { + $options->{grid}{x}{minorGrids} = $grid->{xminor_grids}; + $options->{grid}{x}{overrideOptions} = $axes->xaxis('jsx_grid_options') if $axes->xaxis('jsx_grid_options'); + } + + if ($grid->{ymajor}) { + $options->{grid}{y}{minorGrids} = $grid->{yminor_grids}; + $options->{grid}{y}{overrideOptions} = $axes->yaxis('jsx_grid_options') if $axes->yaxis('jsx_grid_options'); + } + } + + $options->{mathJaxTickLabels} = $axes->style('mathjax_tick_labels') if $xvisible || $yvisible; + + if ($xvisible) { + $options->{xAxis}{name} = $axes->xaxis('label'); + $options->{xAxis}{ticks}{show} = $axes->xaxis('show_ticks'); + $options->{xAxis}{ticks}{labels} = $axes->xaxis('tick_labels'); + $options->{xAxis}{ticks}{labelFormat} = $axes->xaxis('tick_label_format'); + $options->{xAxis}{ticks}{labelDigits} = $axes->xaxis('tick_label_digits'); + $options->{xAxis}{ticks}{scaleSymbol} = $axes->xaxis('tick_scale_symbol'); + $options->{xAxis}{arrowsBoth} = $axes->xaxis('arrows_both'); + $options->{xAxis}{overrideOptions} = $axes->xaxis('jsx_options') if $axes->xaxis('jsx_options'); + } + if ($yvisible) { + $options->{yAxis}{name} = $axes->yaxis('label'); + $options->{yAxis}{ticks}{show} = $axes->yaxis('show_ticks'); + $options->{yAxis}{ticks}{labels} = $axes->yaxis('tick_labels'); + $options->{yAxis}{ticks}{labelFormat} = $axes->yaxis('tick_label_format'); + $options->{yAxis}{ticks}{labelDigits} = $axes->yaxis('tick_label_digits'); + $options->{yAxis}{ticks}{scaleSymbol} = $axes->yaxis('tick_scale_symbol'); + $options->{yAxis}{arrowsBoth} = $axes->yaxis('arrows_both'); + $options->{yAxis}{overrideOptions} = $axes->yaxis('jsx_options') if $axes->yaxis('jsx_options'); + } + + $self->{JS} //= ''; + $plots->{extra_js_code} //= ''; - return <<~ "END_HTML"; + return <<~"END_HTML"; $divs END_HTML @@ -88,7 +170,9 @@ sub HTML { sub get_color { my ($self, $color) = @_; $color = 'default_color' unless $color; - return sprintf("#%02x%02x%02x", @{ $self->plots->colors($color) }); + my $colorParts = $self->plots->colors($color); + return $color unless ref $colorParts eq 'ARRAY'; # Try to use the color by name if it wasn't defined. + return sprintf("#%02x%02x%02x", @$colorParts); } sub get_linestyle { @@ -107,39 +191,95 @@ sub get_linestyle { || 0; } +# Translate pgfplots layers to JSXGraph layers. +# FIXME: JSXGraph layers work rather differently than pgfplots layers. So this is a bit fuzzy, and may need adjustment. +# The layers chosen are as close as possible to the layers that JSXGraph uses by default, although "pre main" and "main" +# don't really have an equivalent. See https://jsxgraph.uni-bayreuth.de/docs/symbols/JXG.Options.html#layer. +# This also does not honor the "axis_on_top" setting. +sub get_layer { + my ($self, $data, $useFillLayer) = @_; + my $layer = $data->style($useFillLayer ? 'fill_layer' : 'layer'); + return unless $layer; + return { + 'axis background' => 0, + 'axis grid' => 1, + 'axis ticks' => 2, + 'axis lines' => 3, + 'pre main' => 4, + 'main' => 5, + 'axis tick labels' => 9, + 'axis descriptions' => 9, + 'axis foreground' => 10 + }->{$layer} // undef; +} + sub get_options { my ($self, $data, %extra_options) = @_; - my $options = Mojo::JSON::encode_json({ - highlight => 0, - strokeColor => $self->get_color($data->style('color')), - strokeWidth => $data->style('width'), - $data->style('start_mark') eq 'arrow' - ? (firstArrow => { type => 4, size => $data->style('arrow_size') || 8 }) - : (), - $data->style('end_mark') eq 'arrow' ? (lastArrow => { type => 4, size => $data->style('arrow_size') || 8 }) - : (), - $data->style('fill') eq 'self' - ? ( - fillColor => $self->get_color($data->style('fill_color') || $data->style('color')), - fillOpacity => $data->style('fill_opacity') - || 0.5 - ) - : (), - dash => $self->get_linestyle($data), - %extra_options, - }); - return $data->style('jsx_options') - ? "JXG.merge($options, " . Mojo::JSON::encode_json($data->style('jsx_options')) . ')' - : $options; + + my $fill = $data->style('fill') || 'none'; + my $drawLayer = $self->get_layer($data); + my $fillLayer = $self->get_layer($data, 1) // $drawLayer; + + my $drawFillSeparate = + $fill eq 'self' + && $data->style('linestyle') ne 'none' + && defined $fillLayer + && (!defined $drawLayer || $drawLayer != $fillLayer); + + my (%drawOptions, %fillOptions); + + if ($data->style('linestyle') ne 'none') { + $drawOptions{layer} = $drawLayer if defined $drawLayer; + $drawOptions{dash} = $self->get_linestyle($data); + $drawOptions{strokeColor} = $self->get_color($data->style('color')); + $drawOptions{strokeWidth} = $data->style('width'); + $drawOptions{firstArrow} = { type => 2, size => $data->style('arrow_size') || 8 } + if $data->style('start_mark') eq 'arrow'; + $drawOptions{lastArrow} = { type => 2, size => $data->style('arrow_size') || 8 } + if $data->style('end_mark') eq 'arrow'; + } + + if ($drawFillSeparate) { + $fillOptions{strokeWidth} = 0; + $fillOptions{layer} = $fillLayer; + $fillOptions{fillColor} = $self->get_color($data->style('fill_color') || $data->style('color')); + $fillOptions{fillOpacity} = $data->style('fill_opacity') || 0.5; + @fillOptions{ keys %extra_options } = values %extra_options; + } elsif ($fill eq 'self') { + if (!%drawOptions) { + $drawOptions{strokeWidth} = 0; + $drawOptions{layer} = $fillLayer if defined $fillLayer; + } + $drawOptions{fillColor} = $self->get_color($data->style('fill_color') || $data->style('color')); + $drawOptions{fillOpacity} = $data->style('fill_opacity') || 0.5; + } elsif ($data->style('name') && $data->style('linestyle') eq 'none') { + # This forces the curve to be drawn invisibly if it has been named, but the linestyle is 'none'. + $drawOptions{strokeWidth} = 0; + } + + @drawOptions{ keys %extra_options } = values %extra_options if %drawOptions; + + my $drawOptions = %drawOptions ? Mojo::JSON::encode_json(\%drawOptions) : ''; + my $fillOptions = $drawFillSeparate ? Mojo::JSON::encode_json(\%fillOptions) : ''; + return ( + $drawOptions && $data->style('jsx_options') + ? "JXG.merge($drawOptions, " . Mojo::JSON::encode_json($data->style('jsx_options')) . ')' + : $drawOptions, + $fillOptions && $data->style('jsx_options') + ? "JXG.merge($fillOptions, " . Mojo::JSON::encode_json($data->style('jsx_options')) . ')' + : $fillOptions + ); } sub add_curve { my ($self, $data) = @_; - return if $data->style('linestyle') eq 'none'; - my $curve_name = $data->style('name'); - my $fill = $data->style('fill') || 'none'; - my $plotOptions = $self->get_options($data, $data->style('polar') ? (curveType => 'polar') : ()); + my $curve_name = $data->style('name'); + warn 'Duplicate plot name detected. This will most likely cause issues. Make sure that all names used are unique.' + if $curve_name && $self->{names}{$curve_name}; + $self->{names}{$curve_name} = 1 if $curve_name; + + my ($plotOptions, $fillOptions) = $self->get_options($data, $data->style('polar') ? (curveType => 'polar') : ()); my $type = 'curve'; my $data_points; @@ -172,68 +312,97 @@ sub add_curve { $data_points = '[[' . join(',', $data->x) . '],[' . join(',', $data->y) . ']]'; } - $self->{JS} .= "const curve_${curve_name} = " if $curve_name; - $self->{JS} .= "board.create('$type', $data_points, $plotOptions);"; - $self->add_point($data, $data->get_start_point, $data->style('width'), $data->style('start_mark')) - if $data->style('start_mark') =~ /circle/; - $self->add_point($data, $data->get_end_point, $data->style('width'), $data->style('end_mark')) - if $data->style('end_mark') =~ /circle/; - + $self->{JS} .= "const curve_${curve_name} = " if $curve_name; + $self->{JS} .= "board.create('$type', $data_points, $plotOptions);" if $plotOptions; + $self->{JS} .= "board.create('$type', $data_points, $fillOptions);" if $fillOptions; + $self->add_point( + $data, $data->get_start_point, + 1.1 * ($data->style('width') || 2), + $data->style('width') || 2, + $data->style('start_mark') + ) if $data->style('linestyle') ne 'none' && $data->style('start_mark') =~ /circle/; + $self->add_point( + $data, $data->get_end_point, + 1.1 * ($data->style('width') || 2), + $data->style('width') || 2, + $data->style('end_mark') + ) if $data->style('linestyle') ne 'none' && $data->style('end_mark') =~ /circle/; + + my $fill = $data->style('fill') || 'none'; if ($fill ne 'none' && $fill ne 'self') { - if ($curve_name) { - my $fill_min = $data->str_to_real($data->style('fill_min')); - my $fill_max = $data->str_to_real($data->style('fill_max')); - my $fillOptions = Mojo::JSON::encode_json({ - strokeWidth => 0, - fillColor => $self->get_color($data->style('fill_color') || $data->style('color')), - fillOpacity => $data->style('fill_opacity') || 0.5, - highlight => 0, - }); - - if ($fill eq 'xaxis') { - $self->{JSend} .= - "const fill_${curve_name} = board.create('curve', [[], []], $fillOptions);" - . "fill_${curve_name}.updateDataArray = function () {" - . "const points = curve_${curve_name}.points"; - if ($fill_min ne '' && $fill_max ne '') { - $self->{JSend} .= - ".filter(p => {" - . "return p.usrCoords[1] >= $fill_min && p.usrCoords[1] <= $fill_max ? true : false" . "})"; + if ($self->{names}{$fill}) { + if ($curve_name) { + my $fill_min = $data->str_to_real($data->style('fill_min')); + my $fill_max = $data->str_to_real($data->style('fill_max')); + my $fill_min_y = $data->str_to_real($data->style('fill_min_y')); + my $fill_max_y = $data->str_to_real($data->style('fill_max_y')); + my $fill_layer = $self->get_layer($data, 1) // $self->get_layer($data); + my $fillOptions = Mojo::JSON::encode_json({ + strokeWidth => 0, + fillColor => $self->get_color($data->style('fill_color') || $data->style('color')), + fillOpacity => $data->style('fill_opacity') || 0.5, + defined $fill_layer ? (layer => $fill_layer) : (), + }); + + if ($fill eq 'xaxis') { + $self->{JS} .= + "const fill_${curve_name} = board.create('curve', [[], []], $fillOptions);" + . "fill_${curve_name}.updateDataArray = function () {" + . "const points = curve_${curve_name}.points"; + if ($fill_min ne '' && $fill_max ne '') { + $self->{JS} .= + ".filter(p => {" + . "return p.usrCoords[1] >= $fill_min && p.usrCoords[1] <= $fill_max ? true : false" . "})"; + } + $self->{JS} .= + ";this.dataX = points.map( p => p.usrCoords[1] );" + . "this.dataY = points.map( p => p.usrCoords[2] );" + . "this.dataX.push(points[points.length - 1].usrCoords[1], " + . "points[0].usrCoords[1], points[0].usrCoords[1]);" + . "this.dataY.push(0, 0, points[0].usrCoords[2]);" . "};" + . "board.update();"; + } else { + $self->{JS} .= + "const fill_${curve_name} = board.create('curve', [[], []], $fillOptions);" + . "fill_${curve_name}.updateDataArray = function () {" + . "const points1 = curve_${curve_name}.points"; + if ($fill_min ne '' && $fill_max ne '') { + $self->{JS} .= + ".filter(p => {" + . "return p.usrCoords[1] >= $fill_min && p.usrCoords[1] <= $fill_max ? true : false" . "})"; + } + if ($fill_min_y ne '' && $fill_max_y ne '') { + $self->{JS} .= + ".filter(p => {" + . "return p.usrCoords[2] >= $fill_min_y && p.usrCoords[2] <= $fill_max_y ? true : false" + . "})"; + } + $self->{JS} .= ";const points2 = curve_${fill}.points"; + if ($fill_min ne '' && $fill_max ne '') { + $self->{JS} .= + ".filter(p => {" + . "return p.usrCoords[1] >= $fill_min && p.usrCoords[1] <= $fill_max ? true : false" . "})"; + } + if ($fill_min_y ne '' && $fill_max_y ne '') { + $self->{JS} .= + ".filter(p => {" + . "return p.usrCoords[2] >= $fill_min_y && p.usrCoords[2] <= $fill_max_y ? true : false" + . "})"; + } + $self->{JS} .= + ";this.dataX = points1.map( p => p.usrCoords[1] ).concat(" + . "points2.map( p => p.usrCoords[1] ).reverse());" + . "this.dataY = points1.map( p => p.usrCoords[2] ).concat(" + . "points2.map( p => p.usrCoords[2] ).reverse());" + . "this.dataX.push(points1[0].usrCoords[1]);" + . "this.dataY.push(points1[0].usrCoords[2]);" . "};" + . "board.update();"; } - $self->{JSend} .= - ";this.dataX = points.map( p => p.usrCoords[1] );" - . "this.dataY = points.map( p => p.usrCoords[2] );" - . "this.dataX.push(points[points.length - 1].usrCoords[1], " - . "points[0].usrCoords[1], points[0].usrCoords[1]);" - . "this.dataY.push(0, 0, points[0].usrCoords[2]);" . "};" - . "board.update();"; } else { - $self->{JSend} .= - "const fill_${curve_name} = board.create('curve', [[], []], $fillOptions);" - . "fill_${curve_name}.updateDataArray = function () {" - . "const points1 = curve_${curve_name}.points"; - if ($fill_min ne '' && $fill_max ne '') { - $self->{JSend} .= - ".filter(p => {" - . "return p.usrCoords[1] >= $fill_min && p.usrCoords[1] <= $fill_max ? true : false" . "})"; - } - $self->{JSend} .= ";const points2 = curve_${fill}.points"; - if ($fill_min ne '' && $fill_max ne '') { - $self->{JSend} .= - ".filter(p => {" - . "return p.usrCoords[1] >= $fill_min && p.usrCoords[1] <= $fill_max ? true : false" . "})"; - } - $self->{JSend} .= - ";this.dataX = points1.map( p => p.usrCoords[1] ).concat(" - . "points2.map( p => p.usrCoords[1] ).reverse());" - . "this.dataY = points1.map( p => p.usrCoords[2] ).concat(" - . "points2.map( p => p.usrCoords[2] ).reverse());" - . "this.dataX.push(points1[0].usrCoords[1]);" - . "this.dataY.push(points1[0].usrCoords[2]);" . "};" - . "board.update();"; + warn q{Unable to create fill. Missing 'name' attribute.}; } } else { - warn "Unable to create fill. Missing 'name' attribute."; + warn q{Unable to fill between curves. Other graph has not yet been drawn.}; } } return; @@ -241,41 +410,78 @@ sub add_curve { sub add_multipath { my ($self, $data) = @_; - return if $data->style('linestyle') eq 'none'; - my @paths = @{ $data->{paths} }; - my $n = scalar(@paths); - my $var = $data->{function}{var}; - my $curve_name = $data->style('name'); - my $plotOptions = $self->get_options($data); - my $jsFunctionx = 'function (x){'; - my $jsFunctiony = 'function (x){'; + my @paths = @{ $data->{paths} }; + my $var = $data->{function}{var}; + my $curve_name = $data->style('name'); + warn 'Duplicate plot name detected. This will most likely cause issues. Make sure that all names used are unique.' + if $curve_name && $self->{names}{$curve_name}; + $self->{names}{$curve_name} = 1 if $curve_name; + my ($plotOptions, $fillOptions) = $self->get_options($data); + + my $count = 0; + unless ($curve_name) { + ++$count while ($self->{names}{"_plots_internal_$count"}); + $curve_name = "_plots_internal_$count"; + $self->{names}{$curve_name} = 1; + } + + $count = 0; + ++$count while ($self->{names}{"${curve_name}_$count"}); + my $curve_parts_name = "${curve_name}_$count"; + $self->{names}{$curve_parts_name} = 1; + + $self->{JS} .= "const $curve_parts_name = [\n"; + + my $cycle = $data->style('cycle'); + my ($start_x, $start_y) = ('', ''); for (0 .. $#paths) { my $path = $paths[$_]; - my $a = $_ / $n; - my $b = ($_ + 1) / $n; - my $tmin = $path->{tmin}; - my $tmax = $path->{tmax}; - my $m = ($tmax - $tmin) / ($b - $a); - my $tmp = $a < 0 ? 'x+' . (-$a) : "x-$a"; - my $t = $m < 0 ? "($tmin$m*($tmp))" : "($tmin+$m*($tmp))"; - - my $xfunction = $data->function_string($path->{Fx}, 'js', $var, undef, $t); - my $yfunction = $data->function_string($path->{Fy}, 'js', $var, undef, $t); - $jsFunctionx .= "if(x<=$b){return $xfunction;}"; - $jsFunctiony .= "if(x<=$b){return $yfunction;}"; + + if (ref $path eq 'ARRAY') { + ($start_x, $start_y) = (', ' . $path->[0], ', ' . $path->[1]) if $cycle && $_ == 0; + $self->{JS} .= "board.create('curve', [[$path->[0]], [$path->[1]]], { visible: false }),\n"; + next; + } + + ($start_x, $start_y) = + (', ' . $path->{Fx}->eval($var => $path->{tmin}), ', ' . $path->{Fy}->eval($var => $path->{tmin})) + if $cycle && $_ == 0; + + my $xfunction = $data->function_string($path->{Fx}, 'js', $var); + my $yfunction = $data->function_string($path->{Fy}, 'js', $var); + + $self->{JS} .= + "board.create('curve', " + . "[(x) => $xfunction, (x) => $yfunction, $path->{tmin}, $path->{tmax}], { visible: false }),\n"; } - $jsFunctionx .= 'return 0;}'; - $jsFunctiony .= 'return 0;}'; - $self->{JS} .= "const curve_${curve_name} = " if $curve_name; - $self->{JS} .= "board.create('curve', [$jsFunctionx, $jsFunctiony, 0, 1], $plotOptions);"; + $self->{JS} .= "];\n"; + + if ($plotOptions) { + $self->{JS} .= <<~"END_JS"; + const curve_$curve_name = board.create('curve', [[], []], $plotOptions); + curve_$curve_name.updateDataArray = function () { + this.dataX = [].concat(...$curve_parts_name.map((c) => c.points.map((p) => p.usrCoords[1]))$start_x); + this.dataY = [].concat(...$curve_parts_name.map((c) => c.points.map((p) => p.usrCoords[2]))$start_y); + }; + END_JS + } + if ($fillOptions) { + $self->{JS} .= <<~"END_JS"; + const fill_$curve_name = board.create('curve', [[], []], $fillOptions); + fill_$curve_name.updateDataArray = function () { + this.dataX = [].concat(...$curve_parts_name.map((c) => c.points.map((p) => p.usrCoords[1]))); + this.dataY = [].concat(...$curve_parts_name.map((c) => c.points.map((p) => p.usrCoords[2]))); + }; + END_JS + } return; } sub add_point { - my ($self, $data, $x, $y, $size, $mark) = @_; + my ($self, $data, $x, $y, $size, $strokeWidth, $mark) = @_; my $color = $self->get_color($data->style('color')); my $fill = $color; @@ -318,7 +524,7 @@ sub add_point { strokeColor => $color, fillColor => $fill, size => $size, - highlight => 0, + strokeWidth => $strokeWidth, showInfoBox => 0, }); $pointOptions = "JXG.merge($pointOptions, " . Mojo::JSON::encode_json($data->style('jsx_options')) . ')' @@ -337,170 +543,77 @@ sub add_points { $data->gen_data if $data->name eq 'function'; for (0 .. $data->size - 1) { - $self->add_point($data, $data->x($_), $data->y($_), $data->style('mark_size') || $data->style('width'), $mark); + $self->add_point( + $data, $data->x($_), $data->y($_), + $data->style('mark_size') || 2, + $data->style('width') || 2, $mark + ); } return; } +sub add_vectorfield { + my ($self, $data) = @_; + my $f = $data->{function}; + my $xfunction = $data->function_string($f->{Fx}, 'js', $f->{xvar}, $f->{yvar}); + my $yfunction = $data->function_string($f->{Fy}, 'js', $f->{xvar}, $f->{yvar}); + + if ($xfunction ne '' && $yfunction ne '') { + my ($options) = $self->get_options( + $data, + scale => $data->style('scale') || 1, + ($data->style('slopefield') ? (arrowhead => { enabled => 0 }) : ()), + ); + $data->update_min_max; + + if ($data->style('normalize') || $data->style('slopefield')) { + my $xtmp = "($xfunction)/Math.sqrt(($xfunction)**2 + ($yfunction)**2)"; + $yfunction = "($yfunction)/Math.sqrt(($xfunction)**2 + ($yfunction)**2)"; + $xfunction = $xtmp; + } + + $self->{JS} .= "board.create('vectorfield', [[(x,y) => $xfunction, (x,y) => $yfunction], " + . "[$f->{xmin}, $f->{xsteps}, $f->{xmax}], [$f->{ymin}, $f->{ysteps}, $f->{ymax}]], $options);"; + } else { + warn 'Vector field not created due to missing JavaScript functions.'; + } +} + sub add_circle { my ($self, $data) = @_; - my $x = $data->x(0); - my $y = $data->y(0); - my $r = $data->style('radius'); - my $linestyle = $self->get_linestyle($data); - my $circleOptions = $self->get_options($data); + my $x = $data->x(0); + my $y = $data->y(0); + my $r = $data->style('radius'); + my ($circleOptions, $fillOptions) = $self->get_options($data); - $self->{JS} .= "board.create('circle', [[$x, $y], $r], $circleOptions);"; + $self->{JS} .= "board.create('circle', [[$x, $y], $r], $circleOptions);" if $circleOptions; + $self->{JS} .= "board.create('circle', [[$x, $y], $r], $fillOptions);" if $fillOptions; return; } sub add_arc { - my ($self, $data) = @_; - my ($x1, $y1) = ($data->x(0), $data->y(0)); - my ($x2, $y2) = ($data->x(1), $data->y(1)); - my ($x3, $y3) = ($data->x(2), $data->y(2)); - my $arcOptions = $self->get_options( + my ($self, $data) = @_; + my ($x1, $y1) = ($data->x(0), $data->y(0)); + my ($x2, $y2) = ($data->x(1), $data->y(1)); + my ($x3, $y3) = ($data->x(2), $data->y(2)); + my ($arcOptions, $fillOptions) = $self->get_options( $data, anglePoint => { visible => 0 }, center => { visible => 0 }, radiusPoint => { visible => 0 }, ); - $self->{JS} .= "board.create('arc', [[$x1, $y1], [$x2, $y2], [$x3, $y3]], $arcOptions);"; - return; -} + # JSXGraph arcs cannot make a 360 degree revolution. So in the case that the start and end point are the same, + # move the end point back around the circle a tiny amount. + if ($x2 == $x3 && $y2 == $y3) { + my $theta = atan2($y2 - $y1, $x2 - $x1) + 2 * 3.14159265358979 - 0.0001; + $x3 = $x1 + cos($theta); + $y3 = $y1 + sin($theta); + } -sub init_graph { - my $self = shift; - my $plots = $self->plots; - my $axes = $plots->axes; - my $xaxis_loc = $axes->xaxis('location'); - my $yaxis_loc = $axes->yaxis('location'); - my $xaxis_pos = $axes->xaxis('position'); - my $yaxis_pos = $axes->yaxis('position'); - my $show_grid = $axes->style('show_grid'); - my $allow_navigation = $axes->style('jsx_navigation') ? 1 : 0; - my ($xmin, $ymin, $xmax, $ymax) = $axes->bounds; - $xaxis_loc = 'bottom' if $xaxis_loc eq 'box'; - $yaxis_loc = 'left' if $yaxis_loc eq 'box'; - - # Determine if zero should be drawn on the axis. - my $x_draw_zero = - $allow_navigation - || ($yaxis_loc eq 'center' && $yaxis_pos != 0) - || ($yaxis_loc eq 'left' && $ymin != 0) - || ($yaxis_loc eq 'right' && $ymax != 0) ? 1 : 0; - my $y_draw_zero = - $allow_navigation - || ($xaxis_loc eq 'middle' && $xaxis_pos != 0) - || ($xaxis_loc eq 'bottom' && $xmin != 0) - || ($xaxis_loc eq 'top' && $xmax != 0) ? 1 : 0; - - # Adjust bounding box to add padding for axes at edge of graph. - $xmin -= 0.11 * ($xmax - $xmin) if $yaxis_loc eq 'left' || $xmin == $yaxis_pos; - $xmax += 0.11 * ($xmax - $xmin) if $yaxis_loc eq 'right' || $xmax == $yaxis_pos; - $ymin -= 0.11 * ($ymax - $ymin) if $xaxis_loc eq 'bottom' || $ymin == $xaxis_pos; - $ymax += 0.11 * ($ymax - $ymin) if $xaxis_loc eq 'top' || $ymax == $xaxis_pos; - - my $JSXOptions = Mojo::JSON::encode_json({ - title => $axes->style('aria_label'), - description => $axes->style('aria_description'), - boundingBox => [ $xmin, $ymax, $xmax, $ymin ], - axis => 0, - showNavigation => $allow_navigation, - pan => { enabled => $allow_navigation }, - zoom => { enabled => $allow_navigation }, - showCopyright => 0, - drag => { enabled => 0 }, - }); - $JSXOptions = "JXG.merge($JSXOptions, " . Mojo::JSON::encode_json($axes->style('jsx_options')) . ')' - if $axes->style('jsx_options'); - my $XAxisOptions = Mojo::JSON::encode_json({ - name => $axes->xaxis('label'), - withLabel => 1, - position => $xaxis_loc eq 'middle' ? ($allow_navigation ? 'sticky' : 'static') : 'fixed', - anchor => $xaxis_loc eq 'top' ? 'left' : $xaxis_loc eq 'bottom' ? 'right' : 'right left', - visible => $axes->xaxis('visible') ? 1 : 0, - highlight => 0, - firstArrow => 0, - lastArrow => { size => 7 }, - straightFirst => $allow_navigation, - straightLast => $allow_navigation, - label => { - anchorX => 'middle', - anchorY => 'middle', - position => '100% left', - offset => [ -10, 0 ], - highlight => 0, - useMathJax => 1 - }, - ticks => { - drawLabels => $axes->xaxis('tick_labels') && $axes->xaxis('show_ticks') ? 1 : 0, - drawZero => $x_draw_zero, - strokeColor => $self->get_color($axes->style('grid_color')), - strokeOpacity => $axes->style('grid_alpha') / 200, - insertTicks => 0, - ticksDistance => $axes->xaxis('tick_delta'), - majorHeight => $axes->xaxis('show_ticks') ? ($show_grid && $axes->xaxis('major') ? -1 : 10) : 0, - minorTicks => $axes->xaxis('minor'), - minorHeight => $axes->xaxis('show_ticks') ? ($show_grid && $axes->xaxis('major') ? -1 : 7) : 0, - label => { - highlight => 0, - anchorX => 'middle', - anchorY => $xaxis_loc eq 'top' ? 'bottom' : 'top', - offset => $xaxis_loc eq 'top' ? [ 0, 3 ] : [ 0, -3 ] - }, - }, - }); - $XAxisOptions = "JXG.merge($XAxisOptions, " . Mojo::JSON::encode_json($axes->xaxis('jsx_options')) . ')' - if $axes->xaxis('jsx_options'); - my $YAxisOptions = Mojo::JSON::encode_json({ - name => $axes->yaxis('label'), - withLabel => 1, - position => $yaxis_loc eq 'center' ? ($allow_navigation ? 'sticky' : 'static') : 'fixed', - anchor => $yaxis_loc eq 'center' ? 'right left' : $yaxis_loc, - visible => $axes->yaxis('visible') ? 1 : 0, - highlight => 0, - firstArrow => 0, - lastArrow => { size => 7 }, - straightFirst => $allow_navigation, - straightLast => $allow_navigation, - label => { - anchorX => 'middle', - anchorY => 'middle', - position => '100% right', - offset => [ 6, -10 ], - highlight => 0, - useMathJax => 1 - }, - ticks => { - drawLabels => $axes->yaxis('tick_labels') && $axes->yaxis('show_ticks') ? 1 : 0, - drawZero => $y_draw_zero, - strokeColor => $self->get_color($axes->style('grid_color')), - strokeOpacity => $axes->style('grid_alpha') / 200, - insertTicks => 0, - ticksDistance => $axes->yaxis('tick_delta'), - majorHeight => $axes->yaxis('show_ticks') ? ($show_grid && $axes->yaxis('major') ? -1 : 10) : 0, - minorTicks => $axes->yaxis('minor'), - minorHeight => $axes->yaxis('show_ticks') ? ($show_grid && $axes->yaxis('major') ? -1 : 7) : 0, - label => { - highlight => 0, - anchorX => $yaxis_loc eq 'right' ? 'left' : 'right', - anchorY => 'middle', - offset => $yaxis_loc eq 'right' ? [ 6, 0 ] : [ -6, 0 ] - }, - }, - }); - $YAxisOptions = "JXG.merge($YAxisOptions, " . Mojo::JSON::encode_json($axes->yaxis('jsx_options')) . ')' - if $axes->yaxis('jsx_options'); - - $self->{JSend} = ''; - $self->{JS} = <<~ "END_JS"; - const board = JXG.JSXGraph.initBoard(id, $JSXOptions); - board.suspendUpdate(); - board.create('axis', [[$xmin, $xaxis_pos], [$xmax, $xaxis_pos]], $XAxisOptions); - board.create('axis', [[$yaxis_pos, $ymin], [$yaxis_pos, $ymax]], $YAxisOptions); - END_JS + $self->{JS} .= "board.create('arc', [[$x1, $y1], [$x2, $y2], [$x3, $y3]], $arcOptions);" if $arcOptions; + $self->{JS} .= "board.create('arc', [[$x1, $y1], [$x2, $y2], [$x3, $y3]], $fillOptions);" if $fillOptions; + return; } sub draw { @@ -508,49 +621,25 @@ sub draw { my $plots = $self->plots; $self->{name} = $plots->get_image_name =~ s/-/_/gr; - $self->init_graph; - - # Plot Data - for my $data ($plots->data('function', 'dataset', 'circle', 'arc', 'multipath')) { + # Plot data, vector/slope fields, and points. Note that points + # are in a separate data call so that they are drawn last. + for my $data ($plots->data('function', 'dataset', 'circle', 'arc', 'multipath', 'vectorfield'), + $plots->data('point')) + { if ($data->name eq 'circle') { $self->add_circle($data); } elsif ($data->name eq 'arc') { $self->add_arc($data); } elsif ($data->name eq 'multipath') { $self->add_multipath($data); + } elsif ($data->name eq 'vectorfield') { + $self->add_vectorfield($data); } else { - $self->add_curve($data); + $self->add_curve($data) unless $data->name eq 'point'; $self->add_points($data); } } - # Vector/Slope Fields - for my $data ($plots->data('vectorfield')) { - my $f = $data->{function}; - my $xfunction = $data->function_string($f->{Fx}, 'js', $f->{xvar}, $f->{yvar}); - my $yfunction = $data->function_string($f->{Fy}, 'js', $f->{xvar}, $f->{yvar}); - - if ($xfunction ne '' && $yfunction ne '') { - my $options = $self->get_options( - $data, - scale => $data->style('scale') || 1, - ($data->style('slopefield') ? (arrowhead => { enabled => 0 }) : ()), - ); - $data->update_min_max; - - if ($data->style('normalize') || $data->style('slopefield')) { - my $xtmp = "($xfunction)/Math.sqrt(($xfunction)**2 + ($yfunction)**2)"; - $yfunction = "($yfunction)/Math.sqrt(($xfunction)**2 + ($yfunction)**2)"; - $xfunction = $xtmp; - } - - $self->{JS} .= "board.create('vectorfield', [[(x,y) => $xfunction, (x,y) => $yfunction], " - . "[$f->{xmin}, $f->{xsteps}, $f->{xmax}], [$f->{ymin}, $f->{ysteps}, $f->{ymax}]], $options);"; - } else { - warn "Vector field not created due to missing JavaScript functions."; - } - } - # Stamps for my $stamp ($plots->data('stamp')) { my $mark = $stamp->style('symbol'); @@ -561,7 +650,7 @@ sub draw { my $y = $stamp->y(0); my $size = $stamp->style('radius') || 4; - $self->add_point($stamp, $x, $y, $size, $mark); + $self->add_point($stamp, $x, $y, $size, $stamp->style('width') || 2, $mark); } # Labels @@ -569,27 +658,40 @@ sub draw { my $str = $label->style('label'); my $x = $label->x(0); my $y = $label->y(0); - my $fontsize = $label->style('fontsize') || 'medium'; + my $fontsize = $label->style('fontsize') || 'normalsize'; my $h_align = $label->style('h_align') || 'center'; my $v_align = $label->style('v_align') || 'middle'; - my $anchor = $v_align eq 'top' ? 'north' : $v_align eq 'bottom' ? 'south' : ''; + my $anchor = $label->style('anchor'); + my $rotate = $label->style('rotate'); + my $padding = $label->style('padding') || 4; my $textOptions = Mojo::JSON::encode_json({ - highlight => 0, - fontSize => { tiny => 8, small => 10, medium => 12, large => 14, giant => 16 }->{$fontsize}, - rotate => $label->style('rotate') || 0, + fontSize => { + tiny => 8, + small => 10, + normalsize => 12, + medium => 12, # deprecated + large => 14, + Large => 16, + giant => 16, # deprecated + Large => 16, + huge => 20, + Huge => 23 + }->{$fontsize}, strokeColor => $self->get_color($label->style('color')), - anchorX => $h_align eq 'center' ? 'middle' : $h_align, - anchorY => $v_align, - cssStyle => 'padding: 3px;', - useMathJax => 1, + $anchor ne '' + ? (angleAnchor => $anchor, anchorX => 'middle', anchorY => 'middle') + : (anchorX => $h_align eq 'center' ? 'middle' : $h_align, anchorY => $v_align), + $rotate ? (rotate => $rotate) : (), + cssStyle => "line-height: 1; padding: ${padding}px;", + useMathJax => 1, }); $textOptions = "JXG.merge($textOptions, " . Mojo::JSON::encode_json($label->style('jsx_options')) . ')' if $label->style('jsx_options'); - $self->{JS} .= "board.create('text', [$x, $y, '$str'], $textOptions);"; + $self->{JS} .= "plot.createLabel($x, $y, '$str', $textOptions);"; } - # JSXGraph only produces HTML graphs and uses TikZ for hadrcopy. + # JSXGraph only produces HTML graphs and uses TikZ for hardcopy. return $self->HTML; } diff --git a/lib/Plots/Plot.pm b/lib/Plots/Plot.pm index 660edba168..844b653f0c 100644 --- a/lib/Plots/Plot.pm +++ b/lib/Plots/Plot.pm @@ -16,27 +16,26 @@ use Plots::Axes; use Plots::Data; use Plots::Tikz; use Plots::JSXGraph; -use Plots::GD; sub new { my ($class, %options) = @_; my $self = bless { - imageName => {}, - width => eval('$main::envir{onTheFlyImageSize}') || 350, - height => undef, - tex_size => 600, - axes => Plots::Axes->new, - colors => {}, - data => [], + imageName => {}, + width => eval('$main::envir{onTheFlyImageSize}') || 350, + height => undef, + tex_size => 600, + rounded_corners => 0, + axes => Plots::Axes->new, + colors => {}, + data => [], + texPackages => [], + tikzLibraries => [] }, $class; # Besides for these core options, pass everything else to the Axes object. - for ('width', 'height', 'tex_size') { - if ($options{$_}) { - $self->{$_} = $options{$_}; - delete $options{$_}; - } + for ('width', 'height', 'tex_size', 'rounded_corners', 'texPackages', 'tikzLibraries') { + $self->{$_} = delete $options{$_} if $options{$_}; } $self->axes->set(%options) if %options; @@ -48,13 +47,12 @@ sub new { sub pgCall { my ($call, @args) = @_; - WeBWorK::PG::Translator::PG_restricted_eval('\&' . $call)->(@args); - return; + return WeBWorK::PG::Translator::PG_restricted_eval('\&' . $call)->(@args); } sub add_js_file { - my ($self, $file) = @_; - pgCall('ADD_JS_FILE', $file); + my ($self, $file, $attributes) = @_; + pgCall('ADD_JS_FILE', $file, 0, $attributes); return; } @@ -164,6 +162,17 @@ sub image_type { my ($self, $type, $ext) = @_; return $self->{type} unless $type; + # Hardcopy uses the Tikz 'pdf' extension and PTX uses the Tikz 'tgz' extension. + if ($self->{pg}{displayMode} eq 'TeX') { + $self->{type} = 'Tikz'; + $self->{ext} = 'pdf'; + return; + } elsif ($self->{pg}{displayMode} eq 'PTX') { + $self->{type} = 'Tikz'; + $self->{ext} = 'tgz'; + return; + } + # Check type and extension are valid. The first element of @validExt is used as default. my @validExt; $type = lc($type); @@ -173,9 +182,6 @@ sub image_type { } elsif ($type eq 'tikz') { $self->{type} = 'Tikz'; @validExt = ('svg', 'png', 'pdf', 'gif', 'tgz'); - } elsif ($type eq 'gd') { - $self->{type} = 'GD'; - @validExt = ('png', 'gif'); } else { warn "Plots: Invalid image type $type."; return; @@ -191,14 +197,6 @@ sub image_type { $self->{ext} = $validExt[0]; } - # Hardcopy uses the Tikz 'pdf' extension and PTX uses the Tikz 'tgz' extension. - if ($self->{pg}{displayMode} eq 'TeX') { - $self->{type} = 'Tikz'; - $self->{ext} = 'pdf'; - } elsif ($self->{pg}{displayMode} eq 'PTX') { - $self->{type} = 'Tikz'; - $self->{ext} = 'tgz'; - } return; } @@ -215,7 +213,7 @@ sub tikz_code { # Add functions to the graph. sub _add_function { - my ($self, $Fx, $Fy, $var, $min, $max, @rest) = @_; + my ($self, $Fx, $Fy, $var, $min, $max, %rest) = @_; $var = 't' unless $var; $Fx = $var unless defined($Fx); @@ -229,9 +227,10 @@ sub _add_function { xmax => $max, color => 'default_color', width => 2, + mark_size => 2, dashed => 0, tikz_smooth => 1, - @rest + %rest ); $self->add_data($data); @@ -297,22 +296,23 @@ sub add_function { sub add_multipath { my ($self, $paths, $var, %options) = @_; my $data = Plots::Data->new(name => 'multipath'); - my $steps = 500; # Steps set high to help Tikz deal with boundaries of paths. - if ($options{steps}) { - $steps = $options{steps}; - delete $options{steps}; - } + my $steps = (delete $options{steps}) || 30; $data->{context} = $self->context; $data->{paths} = [ - map { { - Fx => $data->get_math_object($_->[0], $var), - Fy => $data->get_math_object($_->[1], $var), - tmin => $data->str_to_real($_->[2]), - tmax => $data->str_to_real($_->[3]) - } } @$paths + map { + @$_ == 2 + ? [@$_] + : { + Fx => $data->get_math_object($_->[0], $var), + Fy => $data->get_math_object($_->[1], $var), + tmin => $data->str_to_real($_->[2]), + tmax => $data->str_to_real($_->[3]), + @$_[ 4 .. $#$_ ] + } + } @$paths ]; $data->{function} = { var => $var, steps => $steps }; - $data->style(color => 'default_color', width => 2, %options); + $data->style(color => 'default_color', width => 2, mark_size => 2, %options); $self->add_data($data); return $data; @@ -329,8 +329,9 @@ sub _add_dataset { $data->add(@{ shift(@points) }); } $data->style( - color => 'default_color', - width => 2, + color => 'default_color', + width => 2, + mark_size => 2, @points ); @@ -351,9 +352,10 @@ sub _add_circle { my $data = Plots::Data->new(name => 'circle'); $data->add(@$point); $data->style( - radius => $radius, - color => 'default_color', - width => 2, + radius => $radius, + color => 'default_color', + width => 2, + mark_size => 2, @options ); @@ -374,8 +376,9 @@ sub _add_arc { my $data = Plots::Data->new(name => 'arc'); $data->add($point1, $point2, $point3); $data->style( - color => 'default_color', - width => 2, + color => 'default_color', + width => 2, + mark_size => 2, @options ); @@ -391,23 +394,42 @@ sub add_arc { return $self->_add_arc(@data); } +sub _add_rectangle { + my ($self, $pt0, $pt2, %options) = @_; + unless (ref($pt0) eq 'ARRAY' && @$pt0 == 2 && ref($pt2) eq 'ARRAY' && @$pt2 == 2) { + warn 'A rectangle requires two points defined by length two array references.'; + return; + } + $options{fill} = 'self' if $options{fill_color} && !defined $options{fill}; + return $self->_add_dataset($pt0, [ $pt2->[0], $pt0->[1] ], $pt2, [ $pt0->[0], $pt2->[1] ], $pt0, %options); +} + +sub add_rectangle { + my ($self, @data) = @_; + if (ref($data[0]) eq 'ARRAY' && ref($data[0][0]) eq 'ARRAY') { + return [ map { $self->_add_rectangle(@$_) } @data ]; + } + return $self->_add_rectangle(@data); +} + sub add_vectorfield { my ($self, @options) = @_; my $data = Plots::Data->new(name => 'vectorfield'); $data->set_function( $self->context, - Fx => '', - Fy => '', - xvar => 'x', - yvar => 'y', - xmin => -5, - xmax => 5, - ymin => -5, - ymax => 5, - xsteps => 15, - ysteps => 15, - width => 1, - color => 'default_color', + Fx => '', + Fy => '', + xvar => 'x', + yvar => 'y', + xmin => -5, + xmax => 5, + ymin => -5, + ymax => 5, + xsteps => 15, + ysteps => 15, + width => 1, + mark_size => 1, + color => 'default_color', @options ); @@ -417,15 +439,17 @@ sub add_vectorfield { sub _add_label { my ($self, $x, $y, @options) = @_; - my $data = Plots::Data->new(name => 'label'); + my $data = Plots::Data->new(name => 'label'); + my $label = @options % 2 ? shift @options : ''; $data->add($x, $y); $data->style( color => 'default_color', fontsize => 'medium', orientation => 'horizontal', + rotate => 0, h_align => 'center', v_align => 'middle', - label => '', + label => $label, @options ); @@ -438,22 +462,17 @@ sub add_label { return ref($labels[0]) eq 'ARRAY' ? [ map { $self->_add_label(@$_); } @labels ] : $self->_add_label(@labels); } -# Fill regions only work with GD and are ignored in TikZ images. -sub _add_fill_region { - my ($self, $x, $y, $color) = @_; - my $data = Plots::Data->new(name => 'fill_region'); - $data->add($x, $y); - $data->style(color => $color || 'default_color'); - $self->add_data($data); +sub _add_point { + my ($self, $x, $y, %options) = @_; + $options{marks} = delete $options{mark} if $options{mark} && !defined $options{marks}; + my $data = $self->_add_dataset([ $x, $y ], marks => 'circle', %options); + $data->{name} = 'point'; return $data; } -sub add_fill_region { - my ($self, @regions) = @_; - return - ref($regions[0]) eq 'ARRAY' - ? [ map { $self->_add_fill_region(@$_); } @regions ] - : $self->_add_fill_region(@regions); +sub add_point { + my ($self, @points) = @_; + return ref($points[0]) eq 'ARRAY' ? [ map { $self->_add_point(@$_); } @points ] : $self->_add_point(@points); } sub _add_stamp { @@ -462,7 +481,7 @@ sub _add_stamp { $data->add($x, $y); $data->style( color => 'default_color', - size => 4, + radius => 4, symbol => 'circle', @options ); @@ -485,8 +504,6 @@ sub draw { $image = Plots::Tikz->new($self); } elsif ($type eq 'JSXGraph') { $image = Plots::JSXGraph->new($self); - } elsif ($type eq 'GD') { - $image = Plots::GD->new($self); } else { warn "Undefined image type: $type"; return; diff --git a/lib/Plots/Tikz.pm b/lib/Plots/Tikz.pm index 37cbcd6b78..0df57e9aab 100644 --- a/lib/Plots/Tikz.pm +++ b/lib/Plots/Tikz.pm @@ -15,19 +15,26 @@ use warnings; sub new { my ($class, $plots) = @_; my $image = LaTeXImage->new; - $image->environment([ 'tikzpicture', 'framed' ]); + $image->environment(['tikzpicture']); $image->svgMethod(eval('$main::envir{latexImageSVGMethod}') // 'dvisvgm'); $image->convertOptions(eval('$main::envir{latexImageConvertOptions}') // { input => {}, output => {} }); $image->ext($plots->ext); - $image->tikzLibraries('arrows.meta,plotmarks,backgrounds'); - $image->texPackages(['pgfplots']); - - # Set the pgfplots compatibility, add the pgfplots fillbetween library, set a nice rectangle frame with white - # background for the backgrounds library, and redefine standard layers since the backgrounds library uses layers - # that conflict with the layers used by the fillbetween library. - $image->addToPreamble( <<~ 'END_PREAMBLE'); + $image->texPackages([ 'amsmath', 'pgfplots', @{ $plots->{texPackages} } ]); + + my $tikzLibraries = 'arrows.meta,plotmarks,calc,spath3'; + $tikzLibraries .= ',' . join(',', @{ $plots->{tikzLibraries} }) if @{ $plots->{tikzLibraries} }; + $image->tikzLibraries($tikzLibraries); + + # Set the pgfplots compatibility, add the pgfplots fillbetween library, define a save + # box that is used to wrap the axes in a nice rectangle frame with a white background, and redefine + # standard layers to include a background layer for the background. + # Note that "axis tick labels" is moved after "pre main" and "main" in the standard layer set. That is different + # than the pgfplots defaults, but is consistent with where JSXGraph places them, and is better than what pgplots + # does. Axis tick labels are textual elements that should be in front of the things that are drawn and together + # with the "axis descriptions". + $image->addToPreamble(<<~'END_PREAMBLE'); \usepgfplotslibrary{fillbetween} - \tikzset{inner frame sep = 0pt, background rectangle/.style = { thick, draw = DarkBlue, fill = white }} + \newsavebox{\axesBox} \pgfplotsset{ compat = 1.18, layers/standard/.define layer set = { @@ -36,9 +43,9 @@ sub new { axis grid, axis ticks, axis lines, - axis tick labels, pre main, main, + axis tick labels, axis descriptions, axis foreground }{ @@ -68,7 +75,7 @@ sub new { } END_PREAMBLE - return bless { image => $image, plots => $plots, colors => {} }, $class; + return bless { image => $image, plots => $plots, colors => {}, names => { xaxis => 1 } }, $class; } sub plots { @@ -84,7 +91,9 @@ sub im { sub get_color { my ($self, $color) = @_; return '' if $self->{colors}{$color}; - my ($r, $g, $b) = @{ $self->plots->colors($color) }; + my $colorParts = $self->plots->colors($color); + return '' unless ref $colorParts eq 'ARRAY'; # Try to use the color by name if it wasn't defined. + my ($r, $g, $b) = @$colorParts; $self->{colors}{$color} = 1; return "\\definecolor{$color}{RGB}{$r,$g,$b}\n"; } @@ -94,183 +103,497 @@ sub get_mark { return { circle => '*', closed_circle => '*', - open_circle => 'o', + open_circle => '*, mark options={fill=white}', square => 'square*', - open_square => 'square', + open_square => 'square*, mark options={fill=white}', plus => '+', times => 'x', bar => '|', dash => '-', triangle => 'triangle*', - open_triangle => 'triangle', + open_triangle => 'triangle*, mark options={fill=white}', diamond => 'diamond*', - open_diamond => 'diamond', + open_diamond => 'diamond*, mark options={fill=white}', }->{$mark}; } -sub configure_axes { - my $self = shift; +# This is essentially copied from contextFraction.pl, and is exactly copied from parserGraphTool.pl. +# FIXME: Clearly there needs to be a single version of this somewhere that all three can use. +sub continuedFraction { + my ($x) = @_; + + my $step = $x; + my $n = int($step); + my ($h0, $h1, $k0, $k1) = (1, $n, 0, 1); + + while ($step != $n) { + $step = 1 / ($step - $n); + $n = int($step); + my ($newh, $newk) = ($n * $h1 + $h0, $n * $k1 + $k0); + last if $newk > 10**8; # Bail if the denominator is skyrocketing out of control. + ($h0, $h1, $k0, $k1) = ($h1, $newh, $k1, $newk); + } + + return ($h1, $k1); +} + +sub formatTickLabelText { + my ($self, $value, $axis) = @_; + my $tickFormat = $self->plots->axes->$axis('tick_label_format'); + if ($tickFormat eq 'fraction' || $tickFormat eq 'mixed') { + my ($num, $den) = continuedFraction(abs($value)); + if ($num && $den != 1 && !($num == 1 && $den == 1)) { + if ($tickFormat eq 'fraction' || $num < $den) { + $value = ($value < 0 ? '-' : '') . "\\frac{$num}{$den}"; + } else { + my $int = int($num / $den); + my $properNum = $num % $den; + $value = ($value < 0 ? '-' : '') . "$int\\frac{$properNum}{$den}"; + } + } + } elsif ($tickFormat eq 'scinot') { + my ($mantissa, $exponent) = split('e', sprintf('%e', $value)); + $value = + Plots::Plot::pgCall('Round', $mantissa, $self->plots->axes->$axis('tick_label_digits') // 2) + . "\\cdot 10^{$exponent}"; + } else { + $value = + sprintf('%f', Plots::Plot::pgCall('Round', $value, $self->plots->axes->$axis('tick_label_digits') // 2)); + if ($value =~ /\./) { + $value =~ s/0*$//; + $value =~ s/\.$//; + } + } + my $scaleSymbol = $self->plots->axes->$axis('tick_scale_symbol'); + return '\\(' + . ($value eq '0' ? '0' + : $scaleSymbol ? ($value eq '1' ? $scaleSymbol : $value eq '-1' ? "-$scaleSymbol" : "$value$scaleSymbol") + : $value) . '\\)'; +} + +sub generate_axes { + my ($self, $plotContents) = @_; my $plots = $self->plots; my $axes = $plots->axes; my $grid = $axes->grid; my ($xmin, $ymin, $xmax, $ymax) = $axes->bounds; my ($axes_width, $axes_height) = $plots->size; - my $show_grid = $axes->style('show_grid'); - my $xvisible = $axes->xaxis('visible'); - my $yvisible = $axes->yaxis('visible'); - my $xmajor = $show_grid && $xvisible && $grid->{xmajor} && $axes->xaxis('show_ticks') ? 'true' : 'false'; - my $xminor_num = $grid->{xminor}; - my $xminor = $show_grid && $xvisible && $xmajor eq 'true' && $xminor_num > 0 ? 'true' : 'false'; - my $ymajor = $show_grid && $yvisible && $grid->{ymajor} && $axes->yaxis('show_ticks') ? 'true' : 'false'; - my $yminor_num = $grid->{yminor}; - my $yminor = $show_grid && $yvisible && $ymajor eq 'true' && $yminor_num > 0 ? 'true' : 'false'; - my $xticks = $axes->xaxis('show_ticks') ? "xtick distance=$grid->{xtick_delta}" : 'xtick=\empty'; - my $yticks = $axes->yaxis('show_ticks') ? "ytick distance=$grid->{ytick_delta}" : 'ytick=\empty'; - my $xtick_labels = $axes->xaxis('tick_labels') ? '' : "\nxticklabel=\\empty,"; - my $ytick_labels = $axes->yaxis('tick_labels') ? '' : "\nyticklabel=\\empty,"; - my $grid_color = $axes->style('grid_color'); - my $grid_color2 = $self->get_color($grid_color); - my $grid_alpha = $axes->style('grid_alpha'); - my $xlabel = $axes->xaxis('label'); - my $axis_x_line = $axes->xaxis('location'); - my $axis_x_pos = $axes->xaxis('position'); - my $ylabel = $axes->yaxis('label'); - my $axis_y_line = $axes->yaxis('location'); - my $axis_y_pos = $axes->yaxis('position'); - my $axis_on_top = $axes->style('axis_on_top') ? "axis on top,\n" : ''; - my $hide_x_axis = ''; - my $hide_y_axis = ''; - my $xaxis_plot = ($xmin <= 0 && $xmax >= 0) ? "\\path[name path=xaxis] ($xmin, 0) -- ($xmax,0);\n" : ''; - $axis_x_pos = $axis_x_pos ? ",\naxis x line shift=" . (-$axis_x_pos) : ''; - $axis_y_pos = $axis_y_pos ? ",\naxis y line shift=" . (-$axis_y_pos) : ''; - - unless ($xvisible) { - $xlabel = ''; - $hide_x_axis = "\nx axis line style={draw=none},\n" . "x tick style={draw=none},\n" . "xticklabel=\\empty,"; + my $show_grid = $axes->style('show_grid'); + my $xvisible = $axes->xaxis('visible'); + my $yvisible = $axes->yaxis('visible'); + my $xmajor = $show_grid && $grid->{xmajor} ? 'true' : 'false'; + my $xminor = $show_grid && $xmajor eq 'true' && $grid->{xminor_grids} && $grid->{xminor} > 0 ? 'true' : 'false'; + my $ymajor = $show_grid && $grid->{ymajor} ? 'true' : 'false'; + my $yminor = $show_grid && $ymajor eq 'true' && $grid->{yminor_grids} && $grid->{yminor} > 0 ? 'true' : 'false'; + my $grid_color = $axes->style('grid_color'); + my $grid_color_def = $self->get_color($grid_color); + my $grid_alpha = $axes->style('grid_alpha') / 100; + my $xaxis_location = $axes->xaxis('location'); + my $xaxis_pos = $xaxis_location eq 'middle' ? $axes->xaxis('position') : 0; + my $yaxis_location = $axes->yaxis('location'); + my $yaxis_pos = $yaxis_location eq 'center' ? $axes->yaxis('position') : 0; + my $axis_on_top = $axes->style('axis_on_top') ? "axis on top,\n" : ''; + my $xNegativeArrow = $axes->xaxis('arrows_both') ? 'Latex[{round,scale=1.6}]' : ''; + my $yNegativeArrow = $axes->yaxis('arrows_both') ? 'Latex[{round,scale=1.6}]' : ''; + my $tikz_options = $axes->style('tikz_options') // ''; + + my $xlabel = $xvisible ? $axes->xaxis('label') : ''; + my $xaxis_style = + $xvisible + ? ",\nx axis line style={$xNegativeArrow-Latex[{round,scale=1.6}]}" + : ",\nx axis line style={draw=none},\nextra y ticks={0}"; + my $xtick_style = + $xvisible && $axes->xaxis('show_ticks') ? ",\nx tick style={line width=0.6pt}" : ",\nx tick style={draw=none}"; + + my $ylabel = $yvisible ? $axes->yaxis('label') : ''; + my $yaxis_style = + $yvisible + ? ",\ny axis line style={$yNegativeArrow-Latex[{round,scale=1.6}]}" + : ",\ny axis line style={draw=none},\nextra x ticks={0}"; + my $ytick_style = + $yvisible && $axes->yaxis('show_ticks') ? ",\ny tick style={line width=0.6pt}" : ",\ny tick style={draw=none}"; + + my $x_tick_distance = $axes->xaxis('tick_distance'); + my $x_tick_scale = $axes->xaxis('tick_scale') || 1; + + my $xtick_positions = $axes->xaxis('tick_positions'); + my @xticks; + if (ref $xtick_positions eq 'ARRAY') { + @xticks = @$xtick_positions; + } else { + @xticks = + grep { $_ > $xmin && $_ < $xmax } + map { -$_ * $x_tick_distance * $x_tick_scale } + reverse(1 .. -$xmin / ($x_tick_distance * $x_tick_scale)); + push(@xticks, 0) if $xmin < 0 && $xmax > 0; + push(@xticks, + grep { $_ > $xmin && $_ < $xmax } + map { $_ * $x_tick_distance * $x_tick_scale } (1 .. $xmax / ($x_tick_distance * $x_tick_scale))); + } + + my $xtick_labels_value = $axes->xaxis('tick_labels'); + my $xtick_labels = + $xvisible + && $axes->xaxis('show_ticks') + && $xtick_labels_value + ? ( + ",\nxticklabel shift=9pt,\nxticklabel style={anchor=center},\nxticklabels={" . (join( + ',', + map { + ref $xtick_labels_value eq 'HASH' && defined $xtick_labels_value->{$_} + ? $xtick_labels_value->{$_} + : $self->formatTickLabelText($_ / $x_tick_scale, 'xaxis') + } @xticks + )) + . '}' + ) + : ",\nxticklabel=\\empty"; + + my @xminor_ticks; + if ($grid->{xminor} > 0 && ref $xtick_positions ne 'ARRAY') { + my @majorTicks = @xticks; + unshift(@majorTicks, ($majorTicks[0] // $xmin) - $x_tick_distance * $x_tick_scale); + push(@majorTicks, ($majorTicks[-1] // $xmax) + $x_tick_distance * $x_tick_scale); + my $x_minor_delta = $x_tick_distance * $x_tick_scale / ($grid->{xminor} + 1); + for my $tickIndex (0 .. $#majorTicks - 1) { + push(@xminor_ticks, + grep { $_ > $xmin && $_ < $xmax } + map { $majorTicks[$tickIndex] + $_ * $x_minor_delta } 1 .. $grid->{xminor}); + } + } + + my $y_tick_distance = $axes->yaxis('tick_distance'); + my $y_tick_scale = $axes->yaxis('tick_scale') || 1; + + my $ytick_positions = $axes->yaxis('tick_positions'); + my @yticks; + if (ref $ytick_positions eq 'ARRAY') { + @yticks = @$ytick_positions; + } else { + @yticks = + grep { $_ > $ymin && $_ < $ymax } + map { -$_ * $y_tick_distance * $y_tick_scale } + reverse(1 .. -$ymin / ($y_tick_distance * $y_tick_scale)); + push(@yticks, 0) if $ymin < 0 && $ymax > 0; + push(@yticks, + grep { $_ > $ymin && $_ < $ymax } + map { $_ * $y_tick_distance * $y_tick_scale } (1 .. $ymax / ($y_tick_distance * $y_tick_scale))); } - unless ($yvisible) { - $ylabel = ''; - $hide_y_axis = "\ny axis line style={draw=none},\n" . "y tick style={draw=none},\n" . "yticklabel=\\empty,"; + + my $ytick_labels_value = $axes->yaxis('tick_labels'); + my $ytick_labels = + $yvisible + && $axes->yaxis('show_ticks') + && $ytick_labels_value + ? ( + ",\nyticklabel shift=-3pt,\nyticklabel style={anchor=east},\nyticklabels={" . (join( + ',', + map { + ref $ytick_labels_value eq 'HASH' && defined $ytick_labels_value->{$_} + ? $ytick_labels_value->{$_} + : $self->formatTickLabelText($_ / $y_tick_scale, 'yaxis') + } @yticks + )) + . '}' + ) + : ",\nyticklabel=\\empty"; + + my @yminor_ticks; + if ($grid->{yminor} > 0 && ref $ytick_positions ne 'ARRAY') { + my @majorTicks = @yticks; + unshift(@majorTicks, ($majorTicks[0] // $ymin) - $y_tick_distance * $y_tick_scale); + push(@majorTicks, ($majorTicks[-1] // $ymax) + $y_tick_distance * $y_tick_scale); + my $y_minor_delta = $y_tick_distance * $y_tick_scale / ($grid->{yminor} + 1); + for my $tickIndex (0 .. $#majorTicks - 1) { + push(@yminor_ticks, + grep { $_ > $ymin && $_ < $ymax } + map { $majorTicks[$tickIndex] + $_ * $y_minor_delta } 1 .. $grid->{yminor}); + } } - my $tikzCode = <<~ "END_TIKZ"; - \\begin{axis} - [ - trig format plots=rad, - view={0}{90}, - scale only axis, - height=$axes_height, - width=$axes_width, - ${axis_on_top}axis x line=$axis_x_line$axis_x_pos, - axis y line=$axis_y_line$axis_y_pos, - xlabel={$xlabel}, - ylabel={$ylabel}, - $xticks,$xtick_labels - $yticks,$ytick_labels - xmajorgrids=$xmajor, - xminorgrids=$xminor, - minor x tick num=$xminor_num, - ymajorgrids=$ymajor, - yminorgrids=$yminor, - minor y tick num=$yminor_num, - grid style={$grid_color!$grid_alpha}, - xmin=$xmin, - xmax=$xmax, - ymin=$ymin, - ymax=$ymax,$hide_x_axis$hide_y_axis - ] - $grid_color2$xaxis_plot + + my $xaxis_plot = ($ymin <= 0 && $ymax >= 0) ? "\\path[name path=xaxis] ($xmin, 0) -- ($xmax, 0);" : ''; + $xaxis_pos = $xaxis_pos ? ",\naxis x line shift=" . (($ymin > 0 ? $ymin : $ymax < 0 ? $ymax : 0) - $xaxis_pos) : ''; + $yaxis_pos = $yaxis_pos ? ",\naxis y line shift=" . (($xmin > 0 ? $xmin : $xmax < 0 ? $xmax : 0) - $yaxis_pos) : ''; + + my $roundedCorners = $plots->{rounded_corners} ? 'rounded corners = 10pt' : ''; + my $left = + $yvisible && ($yaxis_location eq 'left' || $yaxis_location eq 'box' || $xmin == $axes->yaxis('position')) + ? 'outer west' + : 'west'; + my $right = $yvisible && ($yaxis_location eq 'right' || $xmax == $axes->yaxis('position')) ? 'outer east' : 'east'; + my $lower = + $xvisible && ($xaxis_location eq 'bottom' || $xaxis_location eq 'box' || $ymin == $axes->xaxis('position')) + ? 'outer south' + : 'south'; + my $upper = $xvisible && ($xaxis_location eq 'top' || $ymax == $axes->xaxis('position')) ? 'outer north' : 'north'; + + # The savebox only actually saves the main layer. All other layers are actually drawn when the savebox is saved. + # So clipping of anything drawn on any other layer has to be done when things are drawn on the other layers. The + # axisclippath is used for this. The main layer is clipped at the end when the savebox is used. + my $tikzCode = <<~"END_TIKZ"; + \\pgfplotsset{set layers=${\($axes->style('axis_on_top') ? 'axis on top' : 'standard')}}% + $grid_color_def + \\savebox{\\axesBox}{ + \\Large + \\begin{axis} + [ + trig format plots=rad, + scale only axis, + height=$axes_height, + width=$axes_width, + ${axis_on_top}axis x line=$xaxis_location$xaxis_pos$xaxis_style, + axis y line=$yaxis_location$yaxis_pos$yaxis_style, + xlabel={$xlabel}, + ylabel={$ylabel}, + xtick={${\(join(',', @xticks))}}$xtick_style$xtick_labels, + minor xtick={${\(join(',', @xminor_ticks))}}, + ytick={${\(join(',', @yticks))}}$ytick_style$ytick_labels, + minor ytick={${\(join(',', @yminor_ticks))}}, + xtick scale label code/.code={}, + ytick scale label code/.code={}, + major tick length=0.3cm, + minor tick length=0.2cm, + xmajorgrids=$xmajor, + xminorgrids=$xminor, + ymajorgrids=$ymajor, + yminorgrids=$yminor, + grid style={$grid_color, opacity=$grid_alpha}, + xmin=$xmin, + xmax=$xmax, + ymin=$ymin, + ymax=$ymax,$tikz_options + ] + $xaxis_plot + \\newcommand{\\axisclippath}{(current axis.south west) [${\( + $roundedCorners && ($lower !~ /^outer/ || $right !~ /^outer/) ? $roundedCorners : 'sharp corners' + )}] -- (current axis.south east) [${\( + $roundedCorners && ($upper !~ /^outer/ || $right !~ /^outer/) ? $roundedCorners : 'sharp corners' + )}] -- (current axis.north east) [${\( + $roundedCorners && ($upper !~ /^outer/ || $left !~ /^outer/) ? $roundedCorners : 'sharp corners' + )}] -- (current axis.north west) [${\( + $roundedCorners && ($lower !~ /^outer/ || $left !~ /^outer/) ? $roundedCorners : 'sharp corners' + )}] -- cycle} + END_TIKZ + + $tikzCode .= $plotContents; + $tikzCode .= $plots->{extra_tikz_code} if $plots->{extra_tikz_code}; + + $tikzCode .= <<~"END_TIKZ"; + \\end{axis} + } + \\pgfresetboundingbox + \\begin{pgfonlayer}{background} + \\filldraw[draw = DarkBlue, fill = white, $roundedCorners, line width = 0.5pt] + (\$(current axis.$left |- current axis.$lower)-(0.25pt,0.25pt)\$) + rectangle + (\$(current axis.$right |- current axis.$upper)+(0.25pt,0.25pt)\$); + \\end{pgfonlayer} + \\begin{scope} + \\clip[$roundedCorners] + (\$(current axis.$left |- current axis.$lower)-(0.25pt,0.25pt)\$) + rectangle + (\$(current axis.$right |- current axis.$upper)+(0.25pt,0.25pt)\$); + \\usebox{\\axesBox} + \\end{scope} + \\begin{pgfonlayer}{axis foreground} + \\draw[draw = DarkBlue, $roundedCorners, line width = 0.5pt, use as bounding box] + (\$(current axis.$left |- current axis.$lower)-(0.25pt,0.25pt)\$) + rectangle + (\$(current axis.$right |- current axis.$upper)+(0.25pt,0.25pt)\$); + \\end{pgfonlayer} END_TIKZ chop($tikzCode); - return $tikzCode =~ s/^\t//gr; + return $tikzCode; } -sub get_plot_opts { +sub get_options { my ($self, $data) = @_; - my $color = $data->style('color') || 'default_color'; - my $width = $data->style('width'); - my $linestyle = $data->style('linestyle') || 'solid'; - my $marks = $data->style('marks') || 'none'; - my $mark_size = $data->style('mark_size') || 0; - my $start = $data->style('start_mark') || 'none'; - my $end = $data->style('end_mark') || 'none'; - my $name = $data->style('name'); - my $fill = $data->style('fill') || 'none'; - my $fill_color = $data->style('fill_color') || $data->style('color') || 'default_color'; - my $fill_opacity = $data->style('fill_opacity') || 0.5; - my $tikz_options = $data->style('tikz_options') ? ', ' . $data->style('tikz_options') : ''; - my $smooth = $data->style('tikz_smooth') ? 'smooth, ' : ''; - - if ($start =~ /circle/) { - $start = '{Circle[sep=-1.196825pt -1.595769' . ($start eq 'open_circle' ? ', open' : '') . ']}'; - } elsif ($start eq 'arrow') { - my $arrow_width = $data->style('arrow_size') || 10; - my $arrow_length = int(1.5 * $arrow_width); - $start = "{Stealth[length=${arrow_length}pt,width=${arrow_width}pt]}"; - } else { - $start = ''; - } - if ($end =~ /circle/) { - $end = '{Circle[sep=-1.196825pt -1.595769' . ($end eq 'open_circle' ? ', open' : '') . ']}'; - } elsif ($end eq 'arrow') { - my $arrow_width = $data->style('arrow_size') || 10; - my $arrow_length = int(1.5 * $arrow_width); - $end = "{Stealth[length=${arrow_length}pt,width=${arrow_width}pt]}"; - } else { - $end = ''; + + my $fill = $data->style('fill') || 'none'; + my $drawLayer = $data->style('layer'); + my $fillLayer = $data->style('fill_layer') || $drawLayer; + my $marks = $self->get_mark($data->style('marks')); + + my $drawFillSeparate = + $fill eq 'self' + && ($data->style('linestyle') ne 'none' || $marks) + && defined $fillLayer + && (!defined $drawLayer || $drawLayer ne $fillLayer); + + my (@drawOptions, @fillOptions); + + if ($data->style('linestyle') ne 'none' || $marks) { + my $linestyle = { + none => 'draw=none', + solid => 'solid', + dashed => 'dash={on 11pt off 8pt phase 6pt}', + short_dashes => 'dash pattern={on 6pt off 3pt}', + long_dashes => 'dash={on 20pt off 15pt phase 10pt}', + dotted => 'dotted', + long_medium_dashes => 'dash={on 20pt off 7pt on 11pt off 7pt phase 10pt}', + }->{ ($data->style('linestyle') || 'solid') =~ s/ /_/gr } + || 'solid'; + push(@drawOptions, $linestyle); + + my $width = $data->style('width'); + push(@drawOptions, "line width=${width}pt", "color=" . ($data->style('color') || 'default_color')); + + if ($linestyle ne 'draw=none') { + my $start = $data->style('start_mark') || ''; + if ($start =~ /circle/) { + $start = + '{Circle[sep=-1.196825pt -1.595769' . ($start eq 'open_circle' ? ', open,fill=white' : '') . ']}'; + } elsif ($start eq 'arrow') { + my $arrow_width = $width * ($data->style('arrow_size') || 8); + $start = "{Stealth[length=${arrow_width}pt 1,width'=0pt 1,inset'=0pt 0.5]}"; + } else { + $start = ''; + } + + my $end = $data->style('end_mark') || ''; + if ($end =~ /circle/) { + $end = '{Circle[sep=-1.196825pt -1.595769' . ($end eq 'open_circle' ? ', open,fill=white' : '') . ']}'; + } elsif ($end eq 'arrow') { + my $arrow_width = $width * ($data->style('arrow_size') || 8); + $end = "{Stealth[length=${arrow_width}pt 1,width'=0pt 1,inset'=0pt 0.5]}"; + } else { + $end = ''; + } + + push(@drawOptions, "$start-$end") if $start || $end; + } + + if ($marks) { + push(@drawOptions, "mark=$marks"); + + my $mark_size = $data->style('mark_size') || 0; + if ($mark_size) { + $mark_size = $mark_size + $width / 2 if $marks =~ /^[*+]/; + $mark_size = $mark_size + $width if $marks eq 'x'; + push(@drawOptions, "mark size=${mark_size}pt"); + } + } + + push(@drawOptions, 'smooth') if $data->style('tikz_smooth'); } - my $end_markers = ($start || $end) ? ", $start-$end" : ''; - $marks = $self->get_mark($marks); - $marks = $marks ? $mark_size ? ", mark=$marks, mark size=${mark_size}px" : ", mark=$marks" : ''; - - $linestyle =~ s/ /_/g; - $linestyle = { - none => ', only marks', - solid => ', solid', - dashed => ', dash={on 11pt off 8pt phase 6pt}', - short_dashes => ', dash pattern={on 6pt off 3pt}', - long_dashes => ', dash={on 20pt off 15pt phase 10pt}', - dotted => ', dotted', - long_medium_dashes => ', dash={on 20pt off 7pt on 11pt off 7pt phase 10pt}', - }->{$linestyle} - || ', solid'; - - if ($fill eq 'self') { - $fill = ", fill=$fill_color, fill opacity=$fill_opacity"; - } else { - $fill = ''; + + my $tikz_options = $data->style('tikz_options'); + + if ($drawFillSeparate) { + my $fill_color = $data->style('fill_color') || $data->style('color') || 'default_color'; + my $fill_opacity = $data->style('fill_opacity') || 0.5; + push(@fillOptions, 'draw=none', "fill=$fill_color", "fill opacity=$fill_opacity"); + push(@fillOptions, 'smooth') if $data->style('tikz_smooth'); + push(@fillOptions, $tikz_options) if $tikz_options; + } elsif ($fill eq 'self') { + if (!@drawOptions) { + push(@drawOptions, 'draw=none'); + $drawLayer = $fillLayer if defined $fillLayer; + } + my $fill_color = $data->style('fill_color') || $data->style('color') || 'default_color'; + my $fill_opacity = $data->style('fill_opacity') || 0.5; + push(@drawOptions, "fill=$fill_color", "fill opacity=$fill_opacity"); + } elsif (!@drawOptions) { + push(@drawOptions, 'draw=none'); } - $name = ", name path=$name" if $name; - return "${smooth}color=$color, line width=${width}pt$marks$linestyle$end_markers$fill$name$tikz_options"; + push(@drawOptions, $tikz_options) if $tikz_options; + + return ([ join(', ', @drawOptions), $drawLayer ], @fillOptions ? [ join(', ', @fillOptions), $fillLayer ] : undef); +} + +sub draw_on_layer { + my ($self, $plot, $layer) = @_; + my $tikzCode; + $tikzCode .= "\\begin{scope}[on layer=$layer]\\begin{pgfonlayer}{$layer}\\clip\\axisclippath;\n" if $layer; + $tikzCode .= $plot; + $tikzCode .= "\\end{pgfonlayer}\\end{scope}\n" if $layer; + return $tikzCode; } sub draw { - my $self = shift; - my $plots = $self->plots; - my $tikzFill = ''; + my $self = shift; + my $plots = $self->plots; # Reset colors just in case. $self->{colors} = {}; - # Add Axes - my $tikzCode = $self->configure_axes; + my $tikzCode = ''; + + # Plot data, vector/slope fields, and points. Note that points + # are in a separate data call so that they are drawn last. + for my $data ($plots->data('function', 'dataset', 'circle', 'arc', 'multipath', 'vectorfield'), + $plots->data('point')) + { + my $color = $data->style('color') || 'default_color'; + my $layer = $data->style('layer'); - # Plot Data - for my $data ($plots->data('function', 'dataset', 'circle', 'arc', 'multipath')) { - my $n = $data->size; - my $color = $data->style('color') || 'default_color'; - my $fill = $data->style('fill') || 'none'; - my $fill_color = $data->style('fill_color') || $data->style('color') || 'default_color'; - my $tikz_options = $self->get_plot_opts($data); $tikzCode .= $self->get_color($color); + + if ($data->name eq 'vectorfield') { + my $f = $data->{function}; + my $xfunction = $data->function_string($f->{Fx}, 'PGF', $f->{xvar}, $f->{yvar}); + my $yfunction = $data->function_string($f->{Fy}, 'PGF', $f->{xvar}, $f->{yvar}); + if ($xfunction ne '' && $yfunction ne '') { + my $width = $data->style('width'); + my $scale = $data->style('scale'); + my $arrows = $data->style('slopefield') ? '' : ', -stealth'; + my $tikz_options = $data->style('tikz_options') ? ', ' . $data->style('tikz_options') : ''; + $data->update_min_max; + + if ($data->style('normalize') || $data->style('slopefield')) { + my $xtmp = "($xfunction)/sqrt(($xfunction)^2 + ($yfunction)^2)"; + $yfunction = "($yfunction)/sqrt(($xfunction)^2 + ($yfunction)^2)"; + $xfunction = $xtmp; + } + + my $yDelta = ($f->{ymax} - $f->{ymin}) / $f->{ysteps}; + my $next = $f->{ymin} + $yDelta; + my $last = $f->{ymax} + $yDelta / 2; # Adjust upward incase of rounding error in the foreach. + my $xSamples = $f->{xsteps} + 1; + $tikzCode .= $self->draw_on_layer( + "\\foreach \\i in {$f->{ymin}, $next, ..., $last}\n" + . "\\addplot[color=$color, line width=${width}pt$arrows, " + . "quiver={u=$xfunction, v=$yfunction, scale arrows=$scale}, samples=$xSamples, " + . "domain=$f->{xmin}:$f->{xmax}$tikz_options] {\\i};\n", + $layer + ); + } else { + warn "Vector field not created due to missing PGF functions."; + } + next; + } + + my $curve_name = $data->style('name'); + warn 'Duplicate plot name detected. This will most likely cause issues. ' + . 'Make sure that all names used are unique.' + if $curve_name && $self->{names}{$curve_name}; + $self->{names}{$curve_name} = 1 if $curve_name; + + my $count = 0; + unless ($curve_name) { + ++$count while ($self->{names}{"_plots_internal_$count"}); + $curve_name = "_plots_internal_$count"; + $self->{names}{$curve_name} = 1; + } + + my $fill = $data->style('fill') || 'none'; + my $fill_color = $data->style('fill_color') || $data->style('color') || 'default_color'; $tikzCode .= $self->get_color($fill_color) unless $fill eq 'none'; + my ($draw_options, $fill_options) = $self->get_options($data); + if ($data->name eq 'circle') { my $x = $data->x(0); my $y = $data->y(0); my $r = $data->style('radius'); - $tikzCode .= "\\draw[$tikz_options] (axis cs:$x,$y) circle [radius=$r];\n"; + $tikzCode .= $self->draw_on_layer( + "\\draw[name path=$curve_name, $draw_options->[0]] (axis cs:$x,$y) circle[radius=$r];\n", + $draw_options->[1]); + $tikzCode .= + $self->draw_on_layer("\\fill[$fill_options->[0]] [spath/use=$curve_name];\n", $fill_options->[1]) + if $fill_options; next; } if ($data->name eq 'arc') { @@ -280,21 +603,32 @@ sub draw { my $r = sqrt(($x2 - $x1)**2 + ($y2 - $y1)**2); my $theta1 = 180 * atan2($y2 - $y1, $x2 - $x1) / 3.14159265358979; my $theta2 = 180 * atan2($y3 - $y1, $x3 - $x1) / 3.14159265358979; - $theta1 += 360 if $theta1 < 0; - $theta2 += 360 if $theta2 < 0; - $tikzCode .= "\\draw[$tikz_options] (axis cs:$x2,$y2) arc ($theta1:$theta2:$r);\n"; + $theta2 += 360 if $theta2 <= $theta1; + $tikzCode .= $self->draw_on_layer( + "\\draw[name path=$curve_name, $draw_options->[0]] (axis cs:$x2,$y2) " + . "arc[start angle=$theta1, end angle=$theta2, radius = $r];\n", + $draw_options->[1] + ); + $tikzCode .= + $self->draw_on_layer("\\fill[$fill_options->[0]] [spath/use=$curve_name];\n", $fill_options->[1]) + if $fill_options; next; } my $plot; + my $plot_options = ''; + if ($data->name eq 'function') { my $f = $data->{function}; if (ref($f->{Fx}) ne 'CODE' && $f->{xvar} eq $f->{Fx}->string) { my $function = $data->function_string($f->{Fy}, 'PGF', $f->{xvar}); if ($function ne '') { $data->update_min_max; - $tikz_options .= ", data cs=polar" if $data->style('polar'); - $tikz_options .= ", domain=$f->{xmin}:$f->{xmax}, samples=$f->{xsteps}"; + my ($axes_xmin, undef, $axes_xmax) = $plots->axes->bounds; + my $min = $data->style('continue') || $data->style('continue_left') ? $axes_xmin : $f->{xmin}; + my $max = $data->style('continue') || $data->style('continue_right') ? $axes_xmax : $f->{xmax}; + $plot_options .= ", data cs=polar" if $data->style('polar'); + $plot_options .= ", domain=$min:$max, samples=$f->{xsteps}"; $plot = "{$function}"; } } else { @@ -302,100 +636,136 @@ sub draw { my $yfunction = $data->function_string($f->{Fy}, 'PGF', $f->{xvar}); if ($xfunction ne '' && $yfunction ne '') { $data->update_min_max; - $tikz_options .= ", domain=$f->{xmin}:$f->{xmax}, samples=$f->{xsteps}"; + $plot_options .= ", domain=$f->{xmin}:$f->{xmax}, samples=$f->{xsteps}"; $plot = "({$xfunction}, {$yfunction})"; } } - } - if ($data->name eq 'multipath') { + } elsif ($data->name eq 'multipath') { my $var = $data->{function}{var}; my @paths = @{ $data->{paths} }; - my $n = scalar(@paths); my @tikzFunctionx; my @tikzFunctiony; + + # This saves the internal path names and the endpoints of the paths. The endpoints are used to determine if + # the paths meet at the endpoints. If the end of one path is not at the same place that the next path + # starts, then the line segment from the first path end to the next path start is inserted. + my @pathData; + + my $count = 0; + for (0 .. $#paths) { my $path = $paths[$_]; - my $a = $_ / $n; - my $b = ($_ + 1) / $n; - my $tmin = $path->{tmin}; - my $tmax = $path->{tmax}; - my $m = ($tmax - $tmin) / ($b - $a); - my $tmp = $a < 0 ? 'x+' . (-$a) : "x-$a"; - my $t = $m < 0 ? "($tmin$m*($tmp))" : "($tmin+$m*($tmp))"; - - my $xfunction = $data->function_string($path->{Fx}, 'PGF', $var, undef, $t); - my $yfunction = $data->function_string($path->{Fy}, 'PGF', $var, undef, $t); - my $last = $_ == $#paths ? '=' : ''; - push(@tikzFunctionx, "(x>=$a)*(x<$last$b)*($xfunction)"); - push(@tikzFunctiony, "(x>=$a)*(x<$last$b)*($yfunction)"); + + ++$count while $self->{names}{"${curve_name}_$count"}; + push(@pathData, ["${curve_name}_$count"]); + $self->{names}{ $pathData[-1][0] } = 1; + + if (ref $path eq 'ARRAY') { + $tikzCode .= + "\\addplot[name path=$pathData[-1][0], draw=none] coordinates {($path->[0], $path->[1])};\n"; + push(@{ $pathData[-1] }, @$path, @$path); + next; + } + + push( + @{ $pathData[-1] }, + $path->{Fx}->eval($var => $path->{tmin}), + $path->{Fy}->eval($var => $path->{tmin}), + $path->{Fx}->eval($var => $path->{tmax}), + $path->{Fy}->eval($var => $path->{tmax}) + ); + + my $xfunction = $data->function_string($path->{Fx}, 'PGF', $var); + my $yfunction = $data->function_string($path->{Fy}, 'PGF', $var); + + my $steps = $path->{steps} // $data->{function}{steps}; + + $tikzCode .= + "\\addplot[name path=$pathData[-1][0], draw=none, domain=$path->{tmin}:$path->{tmax}, " + . "samples=$steps] ({$xfunction}, {$yfunction});\n"; } - $tikz_options .= ", domain=0:1, samples=$data->{function}{steps}"; - $plot = "\n({" . join("\n+", @tikzFunctionx) . "},\n{" . join("\n+", @tikzFunctiony) . '})'; + + $tikzCode .= "\\path[name path=$curve_name] " . join( + ' ', + map { + ( + $_ == 0 || ($pathData[ $_ - 1 ][3] == $pathData[$_][1] + && $pathData[ $_ - 1 ][4] == $pathData[$_][2]) + ? '' + : "-- (spath cs:$pathData[$_ - 1][0] 1) -- (spath cs:$pathData[$_][0] 0) " + ) + . "[spath/append no move=$pathData[$_][0]]" + } 0 .. $#pathData + ) . ($data->style('cycle') ? '-- cycle' : '') . ";\n"; + + $plot = 'skip'; + $tikzCode .= + $self->draw_on_layer("\\draw[$draw_options->[0], spath/use=$curve_name];\n", $draw_options->[1]); } + unless ($plot) { $data->gen_data; - my $tikzData = join(' ', map { '(' . $data->x($_) . ',' . $data->y($_) . ')'; } (0 .. $n - 1)); - $plot = "coordinates {$tikzData}"; + $plot = 'coordinates {' + . join(' ', map { '(' . $data->x($_) . ',' . $data->y($_) . ')'; } (0 .. $data->size - 1)) . '}'; } - $tikzCode .= "\\addplot[$tikz_options] $plot;\n"; + + # 'skip' is a special value of $plot for a multipath which has already been drawn. + $tikzCode .= $self->draw_on_layer("\\addplot[name path=$curve_name, $draw_options->[0]$plot_options] $plot;\n", + $draw_options->[1]) + unless $plot eq 'skip'; + $tikzCode .= $self->draw_on_layer("\\fill[$fill_options->[0]] [spath/use=$curve_name];\n", $fill_options->[1]) + if $fill_options; unless ($fill eq 'none' || $fill eq 'self') { - my $name = $data->style('name'); - if ($name) { - my $opacity = $data->style('fill_opacity') || 0.5; - my $fill_min = $data->style('fill_min'); - my $fill_max = $data->style('fill_max'); - my $fill_range = $fill_min ne '' && $fill_max ne '' ? ", soft clip={domain=$fill_min:$fill_max}" : ''; - $opacity *= 100; - $tikzFill .= "\\addplot[$fill_color!$opacity] fill between[of=$name and $fill$fill_range];\n"; + if ($self->{names}{$fill}) { + # Make sure this is the name from the data style attribute, and not an internal name. + my $name = $data->style('name'); + if ($name) { + my $opacity = $data->style('fill_opacity') || 0.5; + my $fill_min = $data->style('fill_min'); + my $fill_max = $data->style('fill_max'); + my $fill_min_y = $data->style('fill_min_y'); + my $fill_max_y = $data->style('fill_max_y'); + my $fill_reverse = $data->style('fill_reverse'); + my $fill_range = + $fill_min ne '' && $fill_max ne '' && $fill_min_y ne '' && $fill_max_y ne '' + ? ", soft clip={($fill_min, $fill_min_y) rectangle ($fill_max, $fill_max_y)}" + : $fill_min ne '' && $fill_max ne '' ? ", soft clip={domain=$fill_min:$fill_max}" + : $fill_min_y ne '' && $fill_max_y ne '' ? ", soft clip={domain y=$fill_min_y:$fill_max_y}" + : ''; + my $fill_layer = $data->style('fill_layer') || $layer; + my $reverse = $fill_reverse eq '' ? '' : $fill_reverse ? ', reverse' : 'reverse=false'; + $tikzCode .= + "\\begin{scope}[/tikz/fill between/on layer=$fill_layer]\\begin{pgfonlayer}{$fill_layer}" + . "\\clip\\axisclippath;\n" + if $fill_layer; + $tikzCode .= + "\\addplot[$fill_color, fill opacity=$opacity] " + . "fill between[of=$name and $fill$fill_range$reverse];\n"; + $tikzCode .= "\\end{pgfonlayer}\\end{scope}\n" if $fill_layer; + } else { + warn q{Unable to create fill. Missing 'name' attribute.}; + } } else { - warn "Unable to create fill. Missing 'name' attribute."; + warn q{Unable to fill between curves. Other graph has not yet been drawn.}; } } } - # Add fills last to ensure all named graphs have been plotted first. - $tikzCode .= $tikzFill; - - # Vector/Slope Fields - for my $data ($plots->data('vectorfield')) { - my $f = $data->{function}; - my $xfunction = $data->function_string($f->{Fx}, 'PGF', $f->{xvar}, $f->{yvar}); - my $yfunction = $data->function_string($f->{Fy}, 'PGF', $f->{xvar}, $f->{yvar}); - my $arrows = $data->style('slopefield') ? '' : ', -stealth'; - if ($xfunction ne '' && $yfunction ne '') { - my $color = $data->style('color'); - my $width = $data->style('width'); - my $scale = $data->style('scale'); - my $tikz_options = $data->style('tikz_options') ? ', ' . $data->style('tikz_options') : ''; - $data->update_min_max; - - if ($data->style('normalize') || $data->style('slopefield')) { - my $xtmp = "($xfunction)/sqrt(($xfunction)^2 + ($yfunction)^2)"; - $yfunction = "($yfunction)/sqrt(($xfunction)^2 + ($yfunction)^2)"; - $xfunction = $xtmp; - } - - $tikzCode .= $self->get_color($color); - $tikzCode .= - "\\addplot3[color=$color, line width=${width}pt$arrows, " - . "quiver={u=$xfunction, v=$yfunction, scale arrows=$scale}, samples=$f->{xsteps}, " - . "domain=$f->{xmin}:$f->{xmax}, domain y=$f->{ymin}:$f->{ymax}$tikz_options] {1};\n"; - } else { - warn "Vector field not created due to missing PGF functions."; - } - } # Stamps for my $stamp ($plots->data('stamp')) { - my $mark = $self->get_mark($stamp->style('symbol')); + my $mark = $self->get_mark($stamp->style('symbol')) // '*'; next unless $mark; - my $color = $stamp->style('color') || 'default_color'; - my $x = $stamp->x(0); - my $y = $stamp->y(0); - my $r = $stamp->style('radius') || 4; - $tikzCode .= $self->get_color($color) - . "\\addplot[$color, mark=$mark, mark size=${r}pt, only marks] coordinates {($x,$y)};\n"; + my $color = $stamp->style('color') || 'default_color'; + my $x = $stamp->x(0); + my $y = $stamp->y(0); + my $lineWidth = $stamp->style('width') || 2; + my $r = ($stamp->style('radius') || 4) + ($mark =~ /^[*+]/ ? $lineWidth / 2 : $mark eq 'x' ? $lineWidth : 0); + $tikzCode .= + $self->get_color($color) + . "\\addplot[$color, mark=$mark, mark size=${r}pt, line width=${lineWidth}pt, only marks] " + . "coordinates {($x,$y)};\n"; } # Labels @@ -404,30 +774,38 @@ sub draw { my $x = $label->x(0); my $y = $label->y(0); my $color = $label->style('color') || 'default_color'; - my $fontsize = $label->style('fontsize') || 'medium'; + my $fontsize = $label->style('fontsize') || 'normalsize'; my $rotate = $label->style('rotate'); my $tikz_options = $label->style('tikz_options'); my $h_align = $label->style('h_align') || 'center'; my $v_align = $label->style('v_align') || 'middle'; - my $anchor = $v_align eq 'top' ? 'north' : $v_align eq 'bottom' ? 'south' : ''; + my $anchor = $label->style('anchor'); + $anchor = join(' ', + $v_align eq 'top' ? 'north' : $v_align eq 'bottom' ? 'south' : (), + $h_align eq 'left' ? 'west' : $h_align eq 'right' ? 'east' : ()) + if $anchor eq ''; + my $padding = $label->style('padding') || 4; $str = { - tiny => '\tiny ', - small => '\small ', - medium => '', - large => '\large ', - giant => '\Large ', + tiny => '\tiny ', + small => '\small ', + normalsize => '', + medium => '', # deprecated + large => '\large ', + Large => '\Large ', + giant => '\Large ', # deprecated + huge => '\huge ', + Huge => '\Huge ' }->{$fontsize} . $str; - $anchor .= $h_align eq 'left' ? ' west' : $h_align eq 'right' ? ' east' : ''; $tikz_options = $tikz_options ? "$color, $tikz_options" : $color; $tikz_options = "anchor=$anchor, $tikz_options" if $anchor; $tikz_options = "rotate=$rotate, $tikz_options" if $rotate; + $tikz_options = "inner sep=${padding}pt, $tikz_options"; $tikzCode .= $self->get_color($color) . "\\node[$tikz_options] at (axis cs: $x,$y) {$str};\n"; } - $tikzCode .= '\end{axis}'; - $plots->{tikzCode} = $tikzCode; - $self->im->tex($tikzCode); + $plots->{tikzCode} = $self->generate_axes($tikzCode); + $self->im->tex($plots->{tikzCode}); return $plots->{tikzDebug} ? '' : $self->im->draw; } diff --git a/lib/Rserve.pm b/lib/Rserve.pm index 8fb44d999b..414ada95e3 100644 --- a/lib/Rserve.pm +++ b/lib/Rserve.pm @@ -121,7 +121,7 @@ use constant { CMD_attachSession => 0x032, # session key : - # control commands (since 0.6-0) - passed on to the master process */ - # Note: currently all control commands are asychronous, i.e. RESP_OK indicates that the command was enqueued in the + # Note: currently all control commands are asynchronous, i.e. RESP_OK indicates that the command was enqueued in the # master pipe, but there is no guarantee that it will be processed. Moreover non-forked connections (e.g. the # default debug setup) don't process any control commands until the current client connection is closed so the # connection issuing the control command will never see its result. @@ -131,7 +131,7 @@ use constant { CMD_ctrlShutdown => 0x44, # - : - # 'internal' commands (since 0.1-9) - CMD_setBufferSize => 0x081, # [int sendBufSize] this commad allow clients to request bigger buffer sizes if + CMD_setBufferSize => 0x081, # [int sendBufSize] this command allow clients to request bigger buffer sizes if # large data is to be transported from Rserve to the client. (incoming buffer is # resized automatically) CMD_setEncoding => 0x082, # string (one of "native","latin1","utf8") : -; since 0.5-3 diff --git a/lib/Rserve/REXP.pm b/lib/Rserve/REXP.pm index cafb708d54..1e6aed77d6 100644 --- a/lib/Rserve/REXP.pm +++ b/lib/Rserve/REXP.pm @@ -170,6 +170,6 @@ C overloads the stringification, C and C methods. Subclasses -further specialize for their types if necesssary. +further specialize for their types if necessary. =cut diff --git a/lib/Rserve/REXP/Expression.pm b/lib/Rserve/REXP/Expression.pm index bf79977495..a43075047a 100644 --- a/lib/Rserve/REXP/Expression.pm +++ b/lib/Rserve/REXP/Expression.pm @@ -26,7 +26,7 @@ Rserve::REXP::Expression - an R expression vector use Rserve::REXP::Expression - # Representation of the R call C: + # Representation of the R call C: my $vec = Rserve::REXP::Expression->new([ Rserve::REXP::Language->new([ Rserve::REXP::Symbol->new('+'), diff --git a/lib/SampleProblemParser.pm b/lib/SampleProblemParser.pm deleted file mode 100644 index 30b5f8398b..0000000000 --- a/lib/SampleProblemParser.pm +++ /dev/null @@ -1,253 +0,0 @@ -package SampleProblemParser; -use parent qw(Exporter); - -use strict; -use warnings; -use experimental 'signatures'; -use feature 'say'; - -use File::Basename qw(dirname basename); -use File::Find qw(find); -use Pandoc; - -our @EXPORT_OK = qw(parseSampleProblem generateMetadata getSampleProblemCode); - -=head1 NAME - -SampleProblemParser - Parse the documentation in a sample problem in the /doc -directory. - -=head2 C - -Parse a PG file with extra documentation comments. The input is the file and a -hash of global variables: - -=over - -=item C: A reference to a hash which has information (name, directory, -types, subjects, categories) of every sample problem file. - -=item C: A reference to a hash of macros to include as links -within a problem. - -=item C: The root directory of the POD. - -=item C: The url of the pg_doc home. - -=item C: The html url extension (including the dot) to use for pg -doc links. The default is the empty string. - -=back - -=cut - -sub parseSampleProblem ($file, %global) { - my $filename = basename($file); - open(my $FH, '<:encoding(UTF-8)', $file) or do { - warn qq{Could not open file "$file": $!}; - return {}; - }; - my @file_contents = <$FH>; - close $FH; - - my (@blocks, @doc_rows, @code_rows, @description); - my (%options, $descr, $type, $name); - - $global{url_extension} //= ''; - - while (my $row = shift @file_contents) { - chomp($row); - $row =~ s/\t/ /g; - if ($row =~ /^#:%\s*(categor(y|ies)|types?|subjects?|see_also|name)\s*=\s*(.*)\s*$/) { - # skip this, already parsed. - } elsif ($row =~ /^#:%\s*(.*)?/) { - # The row has the form #:% section = NAME. - # This should parse the previous named section and then reset @doc_rows and @code_rows. - push( - @blocks, - { - %options, - doc => pandoc->convert(markdown => 'html', join("\n", @doc_rows)), - code => join("\n", @code_rows) - } - ) if %options; - %options = split(/\s*:\s*|\s*,\s*|\s*=\s*|\s+/, $1); - @doc_rows = (); - @code_rows = (); - } elsif ($row =~ /^#:/) { - # This section is documentation to be parsed. - $row = $row =~ s/^#:\s?//r; - - # Parse any LINK/PODLINK/PROBLINK commands in the documentation. - if ($row =~ /(POD|PROB)?LINK\('(.*?)'\s*(,\s*'(.*)')?\)/) { - my $link_text = defined($1) ? $1 eq 'POD' ? $2 : $global{metadata}{$2}{name} : $2; - my $url = - defined($1) - ? $1 eq 'POD' - ? "$global{pod_root}/" . $global{macro_locations}{ $4 // $2 } - : "$global{pg_doc_home}/$global{metadata}{$2}{dir}/" . ($2 =~ s/.pg$/$global{url_extension}/r) - : $4; - $row = $row =~ s/(POD|PROB)?LINK\('(.*?)'\s*(,\s*'(.*)')?\)/[$link_text]($url)/gr; - } - - push(@doc_rows, $row); - } elsif ($row =~ /^##\s*(END)?DESCRIPTION\s*$/) { - $descr = $1 ? 0 : 1; - } elsif ($row =~ /^##/ && $descr) { - push(@description, $row =~ s/^##\s*//r); - push(@code_rows, $row); - } else { - push(@code_rows, $row); - } - } - - # The last @doc_rows must be parsed then added to the @blocks. - push( - @blocks, - { - %options, - doc => pandoc->convert(markdown => 'html', join("\n", @doc_rows)), - code => join("\n", @code_rows) - } - ); - - return { - name => $global{metadata}{$filename}{name}, - blocks => \@blocks, - code => join("\n", map { $_->{code} } @blocks), - description => join("\n", @description) - }; -} - -=head2 C - -Build a hash of metadata for all PG files in the given directory. A reference -to the hash that is built is returned. - -=cut - -sub generateMetadata ($problem_dir, %options) { - my $index_table = {}; - - find( - { - wanted => sub { - say "Reading file: $File::Find::name" if $options{verbose}; - - if ($File::Find::name =~ /\.pg$/) { - my $metadata = parseMetadata($File::Find::name, $problem_dir); - unless (@{ $metadata->{types} }) { - warn "The type of sample problem is missing for $File::Find::name."; - return; - } - unless ($metadata->{name}) { - warn "The name attribute is missing for $File::Find::name."; - return; - } - $index_table->{ basename($File::Find::name) } = $metadata; - } - } - }, - $problem_dir - ); - - return $index_table; -} - -my @macros_to_skip = qw( - PGML.pl - PGcourse.pl - PGstandard.pl -); - -sub parseMetadata ($path, $problem_dir) { - open(my $FH, '<:encoding(UTF-8)', $path) or do { - warn qq{Could not open file "$path": $!}; - return {}; - }; - my @file_contents = <$FH>; - close $FH; - - my @problem_types = qw(sample technique snippet); - - my $metadata = { dir => (dirname($path) =~ s/$problem_dir\/?//r) =~ s/\/*$//r }; - - while (my $row = shift @file_contents) { - if ($row =~ /^#:%\s*(categor(y|ies)|types?|subjects?|see_also|name)\s*=\s*(.*)\s*$/) { - # The row has the form #:% categories = [cat1, cat2, ...]. - my $label = lc($1); - my @opts = $3 =~ /\[(.*)\]/ ? map { $_ =~ s/^\s*|\s*$//r } split(/,/, $1) : ($3); - if ($label =~ /types?/) { - for my $opt (@opts) { - warn "The type of problem must be one of @problem_types" - unless grep { lc($opt) eq $_ } @problem_types; - } - $metadata->{types} = [ map { lc($_) } @opts ]; - } elsif ($label =~ /^categor/) { - $metadata->{categories} = \@opts; - } elsif ($label =~ /^subject/) { - $metadata->{subjects} = [ map { lc($_) } @opts ]; - } elsif ($label eq 'name') { - $metadata->{name} = $opts[0]; - } elsif ($label eq 'see_also') { - $metadata->{related} = \@opts; - } - } elsif ($row =~ /loadMacros\(/) { - chomp($row); - # Parse the macros, which may be on multiple rows. - my $macros = $row; - while ($row && $row !~ /\);\s*$/) { - $row = shift @file_contents; - chomp($row); - $macros .= $row; - } - # Split by commas and pull out the quotes. - my @macros = map {s/['"\s]//gr} split(/\s*,\s*/, $macros =~ s/loadMacros\((.*)\)\;$/$1/r); - $metadata->{macros} = []; - for my $macro (@macros) { - push(@{ $metadata->{macros} }, $macro) unless grep { $_ eq $macro } @macros_to_skip; - } - } - } - - return $metadata; -} - -=head2 C - -Parse a PG file with extra documentation comments and strip that all out -returning the clean problem code. This returns the same code that the -C returns, except at much less expense as it does not parse -the documentation, it does not require that the metadata be parsed first, and it -does not need macro POD information. - -=cut - -sub getSampleProblemCode ($file) { - my $filename = basename($file); - open(my $FH, '<:encoding(UTF-8)', $file) or do { - warn qq{Could not open file "$file": $!}; - return ''; - }; - my @file_contents = <$FH>; - close $FH; - - my (@code_rows, $inCode); - - while (my $row = shift @file_contents) { - chomp($row); - $row =~ s/\t/ /g; - if ($row =~ /^#:(.*)?/) { - # This is documentation so skip it. - } elsif ($row =~ /^\s*(END)?DOCUMENT.*$/) { - $inCode = $1 ? 0 : 1; - push(@code_rows, $row); - } elsif ($inCode) { - push(@code_rows, $row); - } - } - - return join("\n", @code_rows); -} - -1; diff --git a/lib/Select.pm b/lib/Select.pm index 51f23a6dbe..84a845c6ab 100644 --- a/lib/Select.pm +++ b/lib/Select.pm @@ -75,7 +75,7 @@ receive a sub-set of those questions by using choose. =head2 Usage -=head3 Regualar Select List +=head3 Regular Select List Create a select list using the new_select_list call. diff --git a/lib/Units.pm b/lib/Units.pm index 4436af73b1..924b3c5a0a 100644 --- a/lib/Units.pm +++ b/lib/Units.pm @@ -96,7 +96,7 @@ our %known_units = ( rad => 1, aliases => [ "\x{00B0}", 'degree', 'degrees' ] }, - sr => { # steradian, a mesure of solid angle + sr => { # steradian, a measure of solid angle factor => 1, rad => 2 }, diff --git a/lib/Value.pm b/lib/Value.pm index 72c00fcd7f..57786b808d 100644 --- a/lib/Value.pm +++ b/lib/Value.pm @@ -351,7 +351,7 @@ Usage: Value->Package(name[,noerror]]) -Returns the package name for the specificied Value object class +Returns the package name for the specified Value object class (as specified by the context's {value} hash, or "Value::name"). =cut diff --git a/lib/Value/AnswerChecker.pm b/lib/Value/AnswerChecker.pm index fb4dacb06c..bc2b157196 100644 --- a/lib/Value/AnswerChecker.pm +++ b/lib/Value/AnswerChecker.pm @@ -194,9 +194,9 @@ sub cmp_collect { return 1 unless $self->{ans_name}; $ans->{preview_latex_string} = $ans->{preview_text_string} = ""; my $OK = $self->ans_collect($ans); - $ans->{student_ans} = $self->format_matrix($ans->{student_formula}, @{ $self->{format_options} }, tth_delims => 1); + $ans->{student_ans} = $self->format_matrix($ans->{student_array}, @{ $self->{format_options} }, tth_delims => 1); return 0 unless $OK; - my $array = $ans->{student_formula}; + my $array = $ans->{student_array}; if ($self->{ColumnVector}) { my @V = (); @@ -295,7 +295,7 @@ sub cmp_list_compare { Value::List::cmp_list_compare(@_) } sub typeMatch { my $self = shift; my $other = shift; - return 1 unless ref($other); + return 1 unless Value::isValue($other); $self->type eq $other->type && !$other->isFormula; } @@ -555,57 +555,57 @@ sub format_matrix_HTML { my ($rows, $cols) = (scalar(@{$array}), scalar(@{ $array->[0] })); my $HTML = ""; my $class = 'class="ans_array_cell"'; - my $cell = "display:table-cell;vertical-align:middle;"; + my $cell = "display:table-cell;vertical-align:middle;text-align:center;"; my $pad = "padding:4px 0;"; - if ($sep) { $sep = '' . $sep . '' } - else { $sep = '' } - $sep = '' . $sep . ''; + if ($sep) { $sep = '
    ' . $sep . '
    ' } + else { $sep = '
    ' } + $sep = '
    ' . $sep . '
    '; if ($options{top_labels}) { $HTML .= - '
    ' . join($sep, @{ $options{top_labels} }) - . ''; + . '
    '; } foreach my $i (0 .. $rows - 1) { $HTML .= - '
    ' . join($sep, EVALUATE(@{ $array->[$i] })) - . ''; + . '
    '; } - $HTML = '' . $HTML . ''; + $HTML = '
    ' . $HTML . '
    '; $open = $self->format_delimiter($open, $rows, $options{tth_delims}); $close = $self->format_delimiter($close, $rows, $options{tth_delims}); if ($open ne '' || $close ne '') { my $delim = "display:inline-block; vertical-align:middle;"; $HTML = - '' . $open - . '' + . '
    ' . $HTML - . '' . $close - . ''; + . ''; } - return '' . $HTML - . ''; + . ''; } sub EVALUATE { @@ -689,7 +689,7 @@ sub format_delimiter_tth { # based on these, and keep track of error messages. # -my @ans_cmp_defaults = (showCoodinateHints => 0, checker => sub {0}); +my @ans_cmp_defaults = (showCoordinateHints => 0, checker => sub {0}); sub ans_collect { my $self = shift; @@ -728,8 +728,9 @@ sub ans_collect { } push(@array, [@row]); } - $ans->{student_formula} = [@array]; - $ans->{ans_message} = $ans->{error_message} = ""; + delete $ans->{student_formula}; + $ans->{student_array} = [@array]; + $ans->{ans_message} = $ans->{error_message} = ''; if (scalar(@{$errors})) { $ans->{ans_message} = $ans->{error_message} = '
    ' @@ -851,7 +852,7 @@ sub typeMatch { my $self = shift; my $other = shift; my $ans = shift; - return 1 unless ref($other); + return 1 unless Value::isValue($other); return 0 if Value::isFormula($other); return 1 if $other->type eq 'Infinity' && $ans->{ignoreInfinity}; $self->type eq $other->type; @@ -867,7 +868,7 @@ sub typeMatch { my $self = shift; my $other = shift; my $ans = shift; - return 1 unless ref($other); + return 1 unless Value::isValue($other); return 0 if Value::isFormula($other); return 1 if $other->type eq 'Number'; $self->type eq $other->type; @@ -948,7 +949,7 @@ sub cmp { } # -# Adjust student preview and anser strings so they display properly +# Adjust student preview and answer strings so they display properly # sub cmp_preprocess { my $self = shift; @@ -995,7 +996,7 @@ sub typeMatch { my $self = shift; my $other = shift; my $ans = shift; - return ref($other) && $other->type eq 'Point' && !$other->isFormula; + return Value::isValue($other) && $other->type eq 'Point' && !$other->isFormula; } # @@ -1085,7 +1086,7 @@ sub typeMatch { my $self = shift; my $other = shift; my $ans = shift; - return 0 unless ref($other) && !$other->isFormula; + return 0 unless Value::isValue($other) && !$other->isFormula; return $other->type eq 'Vector' || ($ans->{promotePoints} && $other->type eq 'Point'); } @@ -1213,7 +1214,7 @@ sub typeMatch { my $self = shift; my $other = shift; my $ans = shift; - return 0 unless ref($other) && !$other->isFormula; + return 0 unless Value::isValue($other) && !$other->isFormula; return $other->type eq 'Matrix' || ($other->type =~ m/^(Point|List)$/ && $other->{open} . $other->{close} eq $self->{open} . $self->{close}); @@ -1291,7 +1292,7 @@ Usage: $interval = Interval("(1,2]"); $interval = Interval('(',1,2,']'); - ANS($inteval->cmp); + ANS($interval->cmp); compareOptions and default values: @@ -1333,7 +1334,7 @@ sub cmp_compare { } # -# Check for wrong enpoints and wrong type of endpoints +# Check for wrong endpoints and wrong type of endpoints # sub cmp_postprocess { my $self = shift; @@ -1439,7 +1440,7 @@ package Value::Union; sub typeMatch { my $self = shift; my $other = shift; - return 0 unless ref($other) && !$other->isFormula; + return 0 unless Value::isValue($other) && !$other->isFormula; return $other->length == 2 && ($other->{open} eq '(' || $other->{open} eq '[') @@ -1549,7 +1550,7 @@ sub cmp_defaults { # # Match anything but formulas # -sub typeMatch { return !ref($other) || !$other->isFormula } +sub typeMatch { return !Value::isValue($other) || !$other->isFormula } # # Handle removal of outermost parens in correct answer. @@ -1574,7 +1575,7 @@ sub cmp_equal { $ans->{showPartialCorrectAnswers} = $self->getPG('$showPartialCorrectAnswers'); # - # get the paramaters + # get the parameters # my $showHints = getOption($ans, 'showHints'); my $showLengthHints = getOption($ans, 'showLengthHints'); @@ -1901,7 +1902,7 @@ sub typeMatch { my $self = shift; my $other = shift; my $ans = shift; - return 1 if $self->type eq $other->type; + return 1 if !Value::isValue($other) || $self->type eq $other->type; my $typeMatch = $self->getTypicalValue($self); $other = $self->getTypicalValue($other, 1) if Value::isFormula($other); return 1 unless defined($other); # can't really tell, so don't report type mismatch @@ -2050,48 +2051,44 @@ sub cmp_postprocess { $self->cmp_Error($ans, "The dimension of your result is incorrect"); } -# -# Diagnostics for Formulas -# +# Diagnostics for Formulas. sub cmp_diagnostics { - my $self = shift; - my $ans = shift; - my $isEvaluator = (ref($ans) =~ /Evaluator/) ? 1 : 0; - my $hash = $isEvaluator ? $ans->rh_ans : $ans; - my $diagnostics = $self->{context}->diagnostics->merge("formulas", $self, $hash); + my ($self, $ans) = @_; + my $isEvaluator = ref($ans) =~ /Evaluator/ ? 1 : 0; + my $hash = $isEvaluator ? $ans->rh_ans : $ans; + my $diagnostics = $self->{context}->diagnostics->merge('formulas', $self, $hash); my $formulas = $diagnostics->{formulas}; return unless $formulas->{show}; - my $output = ""; + my $output = ''; if ($isEvaluator) { - # - # The tests to be performed when the answer checker is created - # - $self->getPG('loadMacros("PGgraphmacros.pl")'); - my ($inputs) = $self->getPG('$inputs_ref'); - my $process = $inputs->{checkAnswers} || $inputs->{previewAnswers} || $inputs->{submitAnswers}; - if ($formulas->{checkNumericStability} && !$process) { - ### still needs to be written - } + # The tests to be performed when the answer checker is created. + # Still needs to be written. Commenting it out for now. + #my ($inputs) = $self->getPG('$inputs_ref'); + #my $process = $inputs->{checkAnswers} || $inputs->{previewAnswers} || $inputs->{submitAnswers}; + #if ($formulas->{checkNumericStability} && !$process) { + ### Still needs to be written. + #} } else { - # - # The checks to be performed when an answer is submitted - # + # The checks to be performed when an answer is submitted. my $student = $ans->{student_formula}; - # - # Get the test points - # - my @names = $self->{context}->variables->names; - my $vx = (keys(%{ $self->{variables} }))[0]; - my $vi = 0; - while ($names[$vi] ne $vx) { $vi++ } - my $points = [ map { $_->[$vi] } @{ $self->{test_points} } ]; - my @params = $self->{context}->variables->parameters; - @names = $self->{context}->variables->variables; - # - # The graphs of the functions and errors - # + # Get the test points. This requires first determining what variables are in use and ignore parameters. + my $points = []; + my @params = $self->{context}->variables->parameters; + my %pnames = map { $_ => 1 } @params; + my @names = $self->{context}->variables->variables; + my @variables = grep { !$pnames{$_} } keys %{ $self->{variables} }; + + # If a single variable is found, use its test points for the graph. + # Functions with more than one variable are not graphed so test points are not needed. + if (scalar(@variables) == 1) { + my $vi = 0; + while ($names[$vi] ne $variables[0]) { $vi++ } + $points = [ map { $_->[$vi] } @{ $self->{test_points} } ]; + } + + # The graphs of the functions and errors. if ($formulas->{showGraphs}) { my @G = (); if ($formulas->{combineGraphs}) { @@ -2099,7 +2096,7 @@ sub cmp_diagnostics { @G, $self->cmp_graph( $diagnostics, [ $student, $self ], - title => 'Student Answer (red)
    Correct Answer (green)
    ', + title => 'Student Answer (red)
    Correct Answer (green)', points => $points, showDomain => 1 ) @@ -2131,209 +2128,205 @@ sub cmp_diagnostics { ) ); } - $output .= - '
    ' - . '' - . join('', @G) - . '
    '; + $output .= join('', @G); } - # - # The adaptive parameters - # + # The adaptive parameters. if ($formulas->{showParameters} && scalar(@params) > 0) { - $output .= '
    Adaptive Parameters:
    '; - $output .= join("
    ", map { "  $params[$_]: " . $self->{parameters}[$_] } (0 .. $#params)); - $output .= '
    '; + $output .= + '
    Adaptive Parameters
    ' + . join("
    ", map { "  $params[$_]: " . $self->{parameters}[$_] } 0 .. $#params) + . '
    '; } - # - # The test points and values - # - my @rows = (); - my $colsep = ''; + # The test points and values. + my @rows = (); my @P = (map { (scalar(@{$_}) == 1) ? $_->[0] : $self->Package("Point")->make(@{$_}) } @{ $self->{test_points} }); - my @i = sort { $P[$a] <=> $P[$b] } (0 .. $#P); - foreach $p (@P) { - if (Value::isValue($p) && $p->length > 2) { $p = $p->string; $p =~ s|,|,
    |g } - } - my $zeroLevelTol = $self->{context}{flags}{zeroLevelTol}; - $self->{context}{flags}{zeroLevelTol} = 0; # always show full resolution in the tables below - my $names = join(',', @names); - $names = '(' . $names . ')' if scalar(@names) > 1; - - $student->createPointValues($self->{test_points}, 0, 1, 1) unless $student->{test_values}; - - my $cv = $self->{test_values}; - my $sv = $student->{test_values}; - my $av = $self->{test_adapt} || $cv; - - if ($formulas->{showTestPoints}) { - my @p = ("$names:", (map { $P[ $i[$_] ] } (0 .. $#P))); - push(@rows, '' . join($colsep, @p) . ''); - push(@rows, '' . join($colsep, ("
    ") x scalar(@p)) . ''); - push( - @rows, - '' - . join($colsep, - ($av == $cv) ? "Correct Answer:" : "Adapted Answer:", - map { Value::isNumber($av->[ $i[$_] ]) ? $av->[ $i[$_] ] : "undefined" } (0 .. $#P)) - . '' - ); - push( - @rows, - '' - . join($colsep, - "Student Answer:", - map { Value::isNumber($sv->[ $i[$_] ]) ? $sv->[ $i[$_] ] : "undefined" } (0 .. $#P)) - . '' - ); - } - # - # The absolute errors (colored by whether they are ok or too big) - # - if ($formulas->{showAbsoluteErrors}) { - my @p = ("Absolute Error:"); - my $tolerance = $self->getFlag('tolerance'); - my $tolType = $self->getFlag('tolType'); - my $error; - foreach my $j (0 .. $#P) { - if (Value::isNumber($sv->[ $i[$j] ])) { - $error = CORE::abs($av->[ $i[$j] ] - $sv->[ $i[$j] ]); - $error = - '' - . $error - . '' - if $tolType eq 'absolute'; - } else { - $error = "---"; + if (@P) { + my @i = sort { $P[$a] <=> $P[$b] } 0 .. $#P; + my @points = ([] x scalar(@names)); + for my $p (@P) { + my @pt = Value::isValue($p) ? $p->value : ($p); + for (0 .. $#pt) { + push(@{ $points[$_] }, $pt[$_]); } - push(@p, $error); } - push(@rows, '' . join($colsep, @p) . ''); - } - # - # The relative errors (colored by whether they are OK or too big) - # - if ($formulas->{showRelativeErrors}) { - my @p = ("Relative Error:"); - my $tolerance = $self->getFlag('tolerance'); - my $tol; - my $tolType = $self->getFlag('tolType'); - my $error; - my $zeroLevel = $self->getFlag('zeroLevel'); - foreach my $j (0 .. $#P) { - if (Value::isNumber($sv->[ $i[$j] ])) { - my $c = $av->[ $i[$j] ]; - my $s = $sv->[ $i[$j] ]; - if (CORE::abs($cv->[ $i[$j] ]->value) < $zeroLevel || CORE::abs($s->value) < $zeroLevel) { - $error = CORE::abs($c - $s); - $tol = $zeroLevelTol; + my $zeroLevelTol = $self->{context}{flags}{zeroLevelTol}; + $self->{context}{flags}{zeroLevelTol} = 0; # Always show full resolution in the tables below. + + $student->createPointValues($self->{test_points}, 0, 1, 1) unless $student->{test_values}; + + my $cv = $self->{test_values}; + my $sv = $student->{test_values}; + my $av = $self->{test_adapt} || $cv; + + if ($formulas->{showTestPoints}) { + for my $k (0 .. $#names) { + # Only show variables that are used in either answer or student formula. + push(@rows, [ $names[$k], map { $points[$k][ $i[$_] ] } 0 .. $#P ]) + if $self->{variables}{ $names[$k] } || $student->{variables}{ $names[$k] }; + } + push( + @rows, + [ + $av == $cv ? 'Correct Answer' : 'Adapted Answer', + map { Value::isNumber($av->[ $i[$_] ]) ? $av->[ $i[$_] ] : 'undefined' } 0 .. $#P + ] + ); + push( + @rows, + [ + 'Student Answer', + map { Value::isNumber($sv->[ $i[$_] ]) ? $sv->[ $i[$_] ] : 'undefined' } 0 .. $#P + ] + ); + } + + # The absolute errors (colored by whether they are OK or too big). + if ($formulas->{showAbsoluteErrors}) { + my @p = (); + my $tolerance = $self->getFlag('tolerance'); + my $tolType = $self->getFlag('tolType'); + my $error; + for my $j (0 .. $#P) { + if (Value::isNumber($sv->[ $i[$j] ])) { + $error = CORE::abs($av->[ $i[$j] ] - $sv->[ $i[$j] ]); + $error = + '' + . $error + . '' + if $tolType eq 'absolute'; } else { - $error = CORE::abs(($c - $s) / ($c || 1E-10)); - $tol = $tolerance; + $error = "---"; } - $error = '' . $error . '' - if $tolType eq 'relative'; - } else { - $error = "---"; + push(@p, "$error"); + } + push(@rows, [ 'Absolute Error', @p ]); + } + + # The relative errors (colored by whether they are OK or too big). + if ($formulas->{showRelativeErrors}) { + my @p = (); + my $tolerance = $self->getFlag('tolerance'); + my $tol; + my $tolType = $self->getFlag('tolType'); + my $error; + my $zeroLevel = $self->getFlag('zeroLevel'); + for my $j (0 .. $#P) { + if (Value::isNumber($sv->[ $i[$j] ])) { + my $c = $av->[ $i[$j] ]; + my $s = $sv->[ $i[$j] ]; + if (CORE::abs($cv->[ $i[$j] ]->value) < $zeroLevel || CORE::abs($s->value) < $zeroLevel) { + $error = CORE::abs($c - $s); + $tol = $zeroLevelTol; + } else { + $error = CORE::abs(($c - $s) / ($c || 1E-10)); + $tol = $tolerance; + } + $error = + '' . $error . '' + if $tolType eq 'relative'; + } else { + $error = "---"; + } + push(@p, "$error"); } - push(@p, $error); + push(@rows, [ 'Relative Error', @p ]); } - push(@rows, '' . join($colsep, @p) . ''); + $self->{context}{flags}{zeroLevelTol} = $zeroLevelTol; } - $self->{context}{flags}{zeroLevelTol} = $zeroLevelTol; - # - # Put the data into a table - # + + # Put the data into a table. if (scalar(@rows)) { - $output .= - '


    ' - . join('', @rows) - . '
    '; + my $tdstyle = 'style="padding:5px"'; + my $trstyle = 'style="border:1px solid black"'; + $output .= '

    ' + . ''; + for my $row (@rows) { + my $header = shift(@$row); + $output .= + "'; + } + $output .= '
    $header" + . join("", @$row) + . '
    '; } } - # - # Put all the diagnostic output into a frame - # + + # Put all the diagnostic output into a frame. return unless $output; $output = - '' - . '
    Diagnostics for ' - . $self->string . ':' - . '

    ' + '
    ' + . '

    Diagnostics for ' + . $self->string + . '

    ' . $output - . '

    '; + . ''; $self->getPG('$PG')->debug_message($output); } -# -# Draw a graph from a given Formula object -# +# Draw a graph from a given Formula object. sub cmp_graph { - my $self = shift; - my $diagnostics = shift; - my $F1 = shift; + my ($self, $diagnostics, $F1, @opts) = @_; my $F2; - ($F1, $F2) = @{$F1} if (ref($F1) eq 'ARRAY'); - # - # Get the various options - # - my %options = (title => '', points => [], @_); + ($F1, $F2) = @{$F1} if ref($F1) eq 'ARRAY'; + + # Get the various options. + my %options = (title => '', points => [], @opts); my $graphs = $diagnostics->{graphs}; my $limits = $graphs->{limits}; my $size = $graphs->{size}; - $size = [ $size, $size ] unless ref($size) eq 'ARRAY'; - my $steps = $graphs->{divisions}; - my $points = $options{points}; - my $clip = $options{clip}; + my $steps = $graphs->{divisions}; + my $points = $options{points}; + my $clip = $options{clip}; my ($my, $My) = (0, 0); my ($mx, $Mx); - my $dx; - my $f; - my $y; my @pnames = $self->{context}->variables->parameters; my @pvalues = ($self->{parameters} ? @{ $self->{parameters} } : (0) x scalar(@pnames)); - my $x = ""; + my $x = ''; - # - # Find the max and min values of the function - # - foreach $f ($F1, $F2) { + # Find the max and min values of the function. + for my $f ($F1, $F2) { next unless defined($f); - foreach my $v (keys(%{ $f->{variables} })) { + for my $v (keys(%{ $f->{variables} })) { if ($v ne $x && !$f->{context}->variables->get($v)->{parameter}) { if ($x) { - warn "Only formulas with one variable can be graphed" unless $self->{graphWarning}; + my $warn = $self->{graphWarning}; $self->{graphWarning} = 1; - return ""; + return $warn + ? '' + : '

    ' + . 'Only formulas with one variable can be graphed.
    '; } $x = $v; } } unless ($f->typeRef->{length} == 1) { - warn "Only real-valued functions can be graphed" unless $self->{graphWarning}; + my $warn = $self->{graphWarning}; $self->{graphWarning} = 1; - return ""; + return $warn + ? '' + : '
    ' + . 'Only formulas with one variable can be graphed.
    '; } $x = ($f->{context}->variables->names)[0] unless $x; $limits = [ $self->getVariableLimits($x) ] unless $limits; $limits = $limits->[0] while ref($limits) eq 'ARRAY' && ref($limits->[0]) eq 'ARRAY'; ($mx, $Mx) = @{$limits}; - $dx = ($Mx - $mx) / $steps; + my $dx = ($Mx - $mx) / $steps; if ($f->isConstant) { - $y = $f->eval; + my $y = $f->eval; $my = $y if $y < $my; $My = $y if $y > $My; } else { my $F = $f->perlFunction(undef, [ $x, @pnames ]); - foreach my $i (0 .. $steps - 1) { - $y = eval { &{$F}($mx + $i * $dx, @pvalues) }; + for my $i (0 .. $steps - 1) { + my $y = eval { &{$F}($mx + $i * $dx, @pvalues) }; next unless defined($y) && Value::isNumber($y); $my = $y if $y < $my; $My = $y if $y > $My; @@ -2349,80 +2342,58 @@ sub cmp_graph { } $my = -$My / 10 if $my > -$My / 10; $My = -$my / 10 if $My < -$my / 10; - my $a = $self->Package("Real")->new(($My - $my) / ($Mx - $mx)); - - # - # Create the graph itself, with suitable title - # - my $grf = $self->getPG('$_grf_ = {n => 0}'); - $grf->{Goptions} = [ - $mx, $my, $Mx, $My, - axes => $graphs->{axes}, - grid => $graphs->{grid}, - size => $size, - ]; - $grf->{params} = { - names => [ $x, @pnames ], - values => { map { $pnames[$_] => $pvalues[$_] } (0 .. scalar(@pnames) - 1) }, - }; - $grf->{G} = $self->getPG('init_graph(@{$_grf_->{Goptions}})'); - $grf->{G}->imageName($grf->{G}->imageName . '-' . time()); # avoid browser cache - $self->cmp_graph_function($grf, $F2, "green", $steps, $points) if defined($F2); - $self->cmp_graph_function($grf, $F1, "red", $steps, $points); - my $image = $self->getPG('alias(insertGraph($_grf_->{G}))'); - $image = - ''; + my $a = $self->Package('Real')->new(($My - $my) / ($Mx - $mx)); + + # Create the graph itself, with suitable title. + my $grf = $self->getPG('$_grf_ = {}'); + $grf->{G} = Plots::Plot->new( + xmin => $mx, + xmax => $Mx, + xtick_delta => ($Mx - $mx) / $graphs->{grid}->[0], + xminor => 0, + xlabel => "\\($x\\)", + ymin => $my, + ymax => $My, + ytick_delta => ($My - $my) / $graphs->{grid}->[1], + yminor => 0, + ytick_label_format => CORE::abs($My) < 0.01 ? 'scinot' : 'decimal', + ylabel => '\(f\)' + ); + $grf->{size} = ref($size) eq 'ARRAY' ? { width => $size->[0], height => $size->[1] } : { width => $size }; + $grf->{variable_name} = $x; + $grf->{params} = { map { $pnames[$_] => $pvalues[$_] } 0 .. $#pnames }; + $self->cmp_graph_function($grf, $F2, $mx, $Mx, 'green', $points) if defined($F2); + $self->cmp_graph_function($grf, $F1, $mx, $Mx, 'red', $points); + my $image = '
    ' . $self->getPG('image($_grf_->{G}, %{ $_grf_->{size} })') . '
    '; my $title = $options{title}; - $title .= '
    ' if $title; - $title .= "Domain: [$mx,$Mx]
    " if $options{showDomain}; - $title .= "Range: [$my,$My]
    Aspect ratio: $a:1
    "; - return '' . $image . '
    ' . $title . ''; + $title = "
    $title
    " if $title; + return + '
    ' + . $title + . ($options{showDomain} ? "Domain: [$mx,$Mx]
    " : '') + . "Range: [$my,$My]
    Aspect ratio: $a:1
    " + . $image + . '
    '; } -# -# Add a function to a graph object, and plot the points -# that are used to test the function -# +# Add a function to a Plots::Plot object, and plot the points +# that are used to test the function. sub cmp_graph_function { - my $self = shift; - my $grf = shift; - my $F = shift; - my $color = shift; - my $steps = shift; - my $points = shift; - $grf->{n}++; - my $Fn = "F" . $grf->{n}; - $grf->{$Fn} = $F; - my $f; + my ($self, $grf, $F, $min, $max, $color, $points) = @_; if ($F->isConstant) { my $y = $F->eval; - $f = $self->getPG('new Fun(sub {' . $y . '},$_grf_->{G})'); + $grf->{G}->add_dataset([ $min, $y ], [ $max, $y ], color => $color); } else { - my $X = $grf->{params}{names}[0]; - $f = - $self->getPG('new Fun(sub {Parser::Evaluate($_grf_->{' - . $Fn . '},' - . $X - . '=>shift,%{$_grf_->{params}{values}})},$_grf_->{G})'); - foreach my $x (@{$points}) { - my $y = Parser::Evaluate($F, ($X) => $x, %{ $grf->{params}{values} }); + my $X = $grf->{variable_name}; + $F = $F->substitute(%{ $grf->{params} }) if %{ $grf->{params} }; + $grf->{G}->add_function($F, $X, $min, $max, color => $color); + for my $x (@{$points}) { + my $y = Parser::Evaluate($F, ($X) => $x); next unless defined($y) && Value::isNumber($y); - $grf->{x} = $x; - $grf->{'y'} = $y; - my $C = $self->getPG('new Circle($_grf_->{x},$_grf_->{y},4,"' . $color . '","' . $color . '")'); - $grf->{G}->stamps($C); + $grf->{G}->add_point($x, $y, color => $color); } } - $f->color($color); - $f->weight(2); - $f->steps($steps); } # diff --git a/lib/Value/Context.pm b/lib/Value/Context.pm index 6650b147d6..be4fb592fe 100644 --- a/lib/Value/Context.pm +++ b/lib/Value/Context.pm @@ -96,7 +96,7 @@ sub copy { } # -# Returns the package name for the specificied Value object class +# Returns the package name for the specified Value object class # (as specified by the context's {value} hash, or "Value::name"). # sub Package { diff --git a/lib/Value/Context/Diagnostics.pm b/lib/Value/Context/Diagnostics.pm index e095de3daa..02849dc283 100644 --- a/lib/Value/Context/Diagnostics.pm +++ b/lib/Value/Context/Diagnostics.pm @@ -29,9 +29,8 @@ sub new { graphs => { divisions => 75, limits => undef, - size => 250, + size => 350, grid => [ 10, 10 ], - axes => [ 0, 0 ], }, @_, ); diff --git a/lib/Value/Interval.pm b/lib/Value/Interval.pm index 1691456c5b..978e28ef8c 100644 --- a/lib/Value/Interval.pm +++ b/lib/Value/Interval.pm @@ -204,7 +204,7 @@ sub sub { # # Subtract an interval from another # (returns the resulting interval(s), set -# or nothing for emtpy set) +# or nothing for empty set) # sub subIntervalInterval { my ($l, $r) = @_; diff --git a/lib/Value/Matrix.pm b/lib/Value/Matrix.pm index 18b7498153..4e4d692c5b 100644 --- a/lib/Value/Matrix.pm +++ b/lib/Value/Matrix.pm @@ -12,11 +12,11 @@ This is the Math Object code for a Matrix. =over -=item L +=item L -=item L +=item L -=item L +=item L =back @@ -109,15 +109,19 @@ Examples: column(j) : MathObject Matrix or Real or Complex For a degree 1 Matrix, produces a Real or Complex - For a degree n Matrix with n > 1, produces a degree n Matrix where the 2nd dimesion is length 1 + For a degree n Matrix with n > 1, produces a degree n Matrix where the 2nd dimension is length 1 element : Real/Complex/Fraction value when passed the same number of arguments as the degree of the Matrix. If passed more than n arguments, null. If the degree of the Matrix is n and C is passed - k arguments with k < n, then this produces the corresponding degree (n-k) tensor. + k arguments with k < n, then this produces the corresponding degree (n-k) tensor. =head3 Update values (these need to be added) - see C in MatrixReduce and L + replace(value, [ i, j, ... ]) + For a degree n matrix, the array reference for the second entry should have n elements. + The entry at that location will be replaced with value. + + see C in MatrixReduce and L =head3 Advanced @@ -174,7 +178,7 @@ One can use fractions in Matrices by including C. For exam and operations will be done using rational arithmetic. Also helpful is the method C in the L macro. Some additional information can be -found at L. +found at L. =head2 methods @@ -366,7 +370,7 @@ sub isSquare { =head3 C -Return true if the matix is degree 1 (i.e., is a matrix row) +Return true if the matrix is degree 1 (i.e., is a matrix row) Usage: @@ -621,12 +625,149 @@ sub isRREF { } } else { for my $row (@{ $self->{data} }) { - return 0 unless $row->isREF; + return 0 unless $row->isRREF; } } return 1; } +=head3 C + +Fuzzy, probabilistic check for if two matrices are row equivalent. + +Usage: + + $A = Matrix([3, 1], [1, 0.3333]); + $B = Matrix([1, 1/3], [0, 0]); + $A->isREQ($B); # true + +=cut + +sub isREQ { + my ($l, $r) = @_; + my @ldim = $l->dimensions; + my @rdim = $r->dimensions; + Value::Error('Cannot compare row equivalency of matrices of different degree') unless scalar @ldim == scalar @rdim; + for my $i (0 .. $#ldim) { + Value::Error('Cannot compare row equivalency because matrices differ in the ' + . $l->NameForNumber($i + 1) + . ' dimension') + unless $ldim[$i] == $rdim[$i]; + } + return 1 if $l->isZero && $r->isZero; + return 0 if $l->isZero && !$r->isZero || !$l->isZero && $r->isZero; + + if (scalar @ldim == 1) { + # simply need to determine if rows are parallel + my ($lk, $rk); + for my $i (1 .. $ldim[0]) { + if ($l->element($i) != 0) { + $lk = $l->element($i); + $rk = $r->element($i); + last; + } + } + return $lk * $r == $rk * $l; + } elsif (scalar @ldim == 2) { + my @rgs = $r->NGS; + my @lgs = $l->NGS; + + return 0 if scalar @lgs != scalar @rgs; + + # project each row from $rrows onto @lgs; + # if the complement is nonzero, the row spaces disagree + my @rrows = map { $r->new($_) } $r->value; + for my $v (@rrows) { + my $proj = $r->new((0) x $ldim[1]); + for my $w (@lgs) { + $proj += ($v * $w) * $w; + } + return 0 unless $v == $proj; + } + # and vice versa + my @lrows = map { $l->new($_) } $l->value; + for my $v (@lrows) { + my $proj = $r->new((0) x $rdim[1]); + for my $w (@rgs) { + $proj += ($v * $w) * $w; + } + return 0 unless $v == $proj; + } + + # if we got this far, the row spaces are (fuzzy) equal + # so the matrices are row equivalent + return 1; + } else { + for my $i (1 .. $ldim[0]) { + return 0 unless $l->new($l->{data}[ $i - 1 ])->isREQ($r->new($r->{data}[ $i - 1 ])); + } + return 1; + } +} + +=head3 C + +Normalized Gram Schmidt basis for a given list of vectors. This uses "fuzzy" equivalency to avoid machine rounding +issues. If called as a method on a Matrix, the rows of that Matrix are used as the list of vectors. If called as a +method with the option C<< cols => 1 >>, then the columns are used as the list of vectors. + +In array context, returns an array of 1D Matrix objects. In scalar context, returns a Matrix with those rows. + +If C<< cols => 1 >> was used, return the Matrix objects in column form. + + +Usage: + + $A = Matrix([1, 2, 2], [2, 2, 1]); + + @X = $A->NGS # @X is (Matrix(1, 2, 2)/3, Matrix(10, 2, -7)/sqrt(153)) + @X = Value::Matrix->NGS([1, 2, 2], [2, 2, 1]) # @X is the same as above + $X = $A->NGS # $X is Matrix([1/3, 2/3, 2/3], [10/sqrt(153), 2/sqrt(153), -7/sqrt(153)]) + $X = Value::Matrix->NGS([1, 2, 2], [2, 2, 1]) # $X is the same as above + + @X = $A->NGS(cols => 1) # @X is (Matrix([1/sqrt(5)], [2/sqrt(5)]), Matrix([2/sqrt(5)], [-1/sqrt(5)])) + $X = $A->NGS(cols => 1) # $X is Matrix([1/sqrt(5), 2/sqrt(5)], [2/sqrt(5), -1/sqrt(5)]) + +=cut + +sub NGS { + my ($self, @args) = @_; + my %options; + my @rows; + if (ref($self) eq 'Value::Matrix') { + %options = @args; + @rows = $options{cols} ? $self->transpose->value : $self->value; + } else { + @rows = @args; + } + Value::Error('You must provide vectors to apply Gram Schmidt to') if !@rows; + @rows = map { Value::Matrix->new($_) } @rows; + @rows = grep { !$_->isZero } @rows; + Value::Error('You must provide at least one nonzero row for Gram Schmidt') unless @rows; + my $n = ($rows[0]->dimensions)[0]; + for my $r (@rows) { + Value::Error('Rows provided for Gram Schmidt should not be nested arrays') unless $r->degree == 1; + Value::Error('All rows provided for Gram Schmidt should have the same length') unless ($r->dimensions)[0] == $n; + } + @rows = map { $_ / ($_ * $_)**0.5 } @rows; + my @gs = ($rows[0]); + for my $i (1 .. $#rows) { + my $proj = Value::Matrix->new((0) x $n); + for my $v (@gs) { + $proj += ($rows[$i] * $v) * $v; + } + my $gs = $rows[$i] - $proj; + push @gs, $gs / ($gs * $gs)**0.5 unless $rows[$i] == $proj; + } + return + $options{cols} + ? wantarray + ? map { $_->transpose } @gs + : Value::Matrix->new(@gs)->transpose + : wantarray ? @gs + : Value::Matrix->new(@gs); +} + sub _isNumber { my $n = shift; return Value::isNumber($n) || Value::classMatch($n, 'Fraction'); @@ -1052,7 +1193,8 @@ perform row operations. =item * Row Swap -To perform a row swap between rows C and C, then C. +The method C<< Value::Matrix->E(n,[i, j]) >> returns the n by n elementary matrix that +upon right multiplication performs the row swap between rows C and C. Usage: @@ -1072,7 +1214,8 @@ where the size of the resulting matrix is the number of rows of C<$A>. =item * Multiply a row by a constant -To create the matrix that will multiply a row C, by constant C, then C +The method C<< Value::Matrix->E(n, [i], k) >> returns the n by n elementary matrix that upon +right multiplication will multiply a row C, by constant C. Usage: @@ -1081,7 +1224,7 @@ Usage: generates the matrix [ [ 1, 0, 0, 0 ], - [ 0, 4, 0, 0 ], + [ 0, 3, 0, 0 ], [ 0, 0, 1, 0 ], [ 0, 0, 0, 1 ] ] @@ -1093,7 +1236,8 @@ will generate the elementary matrix of size number of rows of C<$A>, which multi =item * Multiply a row by a constant and add to another row. -To create the matrix that will multiply a row C, by constant C and add to row C then C +The method C<< Value::Matrix->E(n, [ i, j ], k) >> returns the n by n elementary matrix that upon +right multiplication will replace row C with k times row C added to row C. Usage: @@ -1102,15 +1246,16 @@ Usage: generates the matrix: [ [ 1, 0, 0, 0 ], - [ 0, 1, 0, 0 ], - [ 0, -3, 1, 0 ], + [ 0, 1, -3, 0 ], + [ 0, 0, 1, 0 ], [ 0, 0, 0, 1 ] ] -or if the matrix C<$A> exists then +or if the matrix C<$A> exists of size m by n then $A->E([3, 4], -5); -will generate the elementary matrix of size number of rows of C<$A>, which multiplies row 3 by -5 and adds to row 4. +will generate the m by m elementary matrix which replaces row 4 by -5 times row 3 added to row 4. +If C<$A> does not have at least 4 rows, an error is raised. =back @@ -1147,7 +1292,7 @@ sub E { if (@ij == 1) { $row[$i] = $k if ($i == $ij[0]); } elsif (defined $k) { - $row[ $ij[1] ] = $k if ($i == $ij[0]); + $row[ $ij[0] ] = $k if ($i == $ij[1]); } else { ($row[ $ij[0] ], $row[ $ij[1] ]) = ($row[ $ij[1] ], $row[ $ij[0] ]) if ($i == $ij[0] || $i == $ij[1]); } @@ -1338,6 +1483,197 @@ sub element { return $M->extract(@_); } +=head3 C + +Replace an entry with a new element and return the replaced element. + +Usage: + + $A = Matrix([ [ 1, 2, 3 ], [ 4, 5, 6 ] ]); + $A->replace(7, [ 2, 1 ]); + # Now $A is the matrix [ [ 1, 2, 3 ], [ 7, 5, 6 ] ] + + # Also, the method returns the value that is replaced. + $x = $A->replace(8, [ 1, 3 ]); + # Now $A is [ [ 1, 2, 8 ], [ 7, 5, 6 ] ] and $x is 3. + + # It is also OK to specify the indices as an array instead of an array reference + $A->replace(7, 2, 1); + +=cut + +sub replace { + my ($self, $value, @indices) = @_; + $value = Value::makeValue($value) unless Value::isValue($value); + $self->Error('Cannot replace a matrix entry with a Formula') if Value::isFormula($value); + my $dim = $self->degree; + @indices = @{ $indices[0] } if (ref $indices[0] eq 'ARRAY'); + $self->Error("There should be $dim indices") unless $dim == @indices; + my $data = $self->{data}; + my $i = pop(@indices) - 1; + + for (my $n = 0; @indices; $n++) { + my $j = shift(@indices) - 1; + $self->Error('The ' . $self->NameForNumber($n + 1) . ' index is outside of the array bounds') + if $j < 0 || $j >= scalar(@$data); + $data = $data->[$j]->{data}; + } + $self->Error('The last index is outside of the array bounds') + if $i < 0 || $i >= scalar(@$data); + $self->Error('The new entry value should be ' . $data->[$i]->showType . ' not ' . $value->showType) + unless $value->type eq $data->[$i]->type; + my $old = $data->[$i]; + $data->[$i] = $value; + return $old; +} + +=head3 C + +Return a submatrix of a Matrix. If the indices are array referencess, the rows, columns, +etc. that are indicated are kept and used to return a Matrix of the same degree. Other +rows, columns, etc. are removed. + +Note this can be used to permute rows, columns, etc by specifying all rows, columns, etc. +in a different order. + +Each input can be an integer instead of an array reference, and the complement of that +integer is used for the rows to keep. In other words, using an integer for input indicates +that you wish for that one row, column, etc. to be removed. Using 0 as an input indicates +that all rows, columns, etc. for that dimension should be kept. + +You can mix and match using array references and integers. + +Usage: + + $A = Matrix([ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ]); + $A->subMatrix([2 .. 3], [2 .. 4]); # returns Matrix([ 6, 7, 8 ], [ 10, 11, 12 ]) + $A->subMatrix(2, 3); # returns Matrix([ 1, 2, 4 ], [ 9, 10, 12 ]); + $A->subMatrix([1], 0); # returns Matrix([ [ 1, 2, 3, 4 ] ]); + $A->subMatrix([3, 1, 2], [1, 4, 2]); # returns Matrix([ 9, 12, 10 ], [ 1, 4, 2 ] ,[ 5, 8, 6 ]); + +This can also be used on Matrix objects that are not degree 2. + + $B = Matrix(2, 4, 6, 8); + $B->subMatrix([1, 3]); # returns Matrix(2, 6); + $B->subMatrix(2); # returns Matrix(2, 6, 8); + + $C = Matrix([ [ 1, 2, 3 ], [ 4, 5, 6 ] ], [ [ 7, 8, 9 ], [ 10, 11, 12 ] ]); + $C->subMatrix(0, 1, [1, 3]); # returns Matrix([ [ 4, 6 ] ], [ [ 10, 12 ] ]); + $C->subMatrix(1, 2, 3); # returns Matrix([ [ [ 7, 8 ] ] ]); + +=cut + +sub subMatrix { + my ($self, @ind) = @_; + my @dim = $self->dimensions; + my $degree = scalar @dim; + + # indices to keep for submatrix + my @indices; + + # check that the input is appropriate for the size of the matrix + Value::Error("There must be $degree arguments") unless $#dim == $#ind; + + # convert any integer arguments to array references + for my $i (0 .. $#ind) { + if (ref $ind[$i] eq 'ARRAY') { + push @indices, $ind[$i]; + } else { + # check that $ind[$i] is an integer in the appropriate range + Value::Error("The input $ind[$i] is not a valid index") + unless $ind[$i] =~ /^\d+$/ && $ind[$i] >= 0 && $ind[$i] <= $dim[$i]; + push @indices, [ grep { $_ != $ind[$i] } (1 .. $dim[$i]) ]; + } + } + + # check that all indices are integers in the appropriate range and that all array references are nonempty + for my $i (0 .. $#indices) { + Value::Error("Cannot use empty array reference for indices to keep") unless (@{ $indices[$i] }); + for my $j (@{ $indices[$i] }) { + Value::Error("The input $j is not a valid index") unless $j =~ /^\d+$/ && $j >= 1 && $j <= $dim[$i]; + } + } + + sub extractElements { + my ($self, $indices, $elements) = @_; + + # these need to be copies of the array arguments + my @ind_copy = @$indices; + my @elements_copy = @$elements; + + my $ind = shift @elements_copy; + push(@ind_copy, [ 1 .. scalar(@$ind) ]); + + my @M; + for my $i (@$ind) { + push(@M, + ref $self->element($i) eq 'Value::Matrix' + ? $self->element($i)->extractElements(\@ind_copy, \@elements_copy) + : $self->element($i)); + } + + return $self->make($self->context, @M); + } + + return $self->extractElements([], \@indices); +} + +=head3 C + +Return a new Matrix, where a row has been removed from a Matrix. This is only valid for Matrix +Math Objects with degree 2 or higher. Removing a ith "row" from a Matrix of degree 3 or higher +means to remove all entries with first index i. + +Usage: + + $A = Matrix([ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ], [13, 14, 15, 16]); + $A->removeRow(3); # returns Matrix([ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [13, 14, 15, 16]); + + $B = Matrix([ [ 1, 2, 3 ], [ 4, 5, 6 ] ], [ [ 7, 8, 9 ], [ 10, 11, 12 ] ]); + $B->removeRow(2); # returns Matrix([ [ [ 1, 2, 3 ], [ 4, 5, 6 ] ] ]); + +=cut + +sub removeRow { + my ($self, $r) = @_; + my @dim = $self->dimensions; + my $degree = scalar @dim; + Value::Error("removeRow cannot be used on a Matrix of degree 1") if $degree == 1; + Value::Error("cannot remove a Matrix's only row") if $dim[0] == 1; + my @indices = map { [ 1 .. $_ ] } @dim; + Value::Error("Can only remove rows 1 through $indices[0][-1]") + unless $r =~ /^\d+$/ && $r >= 1 && $r <= $indices[0][-1]; + return $self->subMatrix([ grep { $_ != $r } @{ $indices[0] } ], @indices[ 1 .. $#indices ]); +} + +=head3 C + +Return a new Matrix, where a column has been removed from a Matrix. This is only valid for Matrix +Math Objects with degree 2 or higher. Removing a jth "column" from a Matrix of degree 3 or higher +means to remove all entries with second index j. + +Usage: + + $A = Matrix([ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ], [13, 14, 15, 16]); + $A->removeColumn(3); # returns Matrix([ 1, 2, 4 ], [ 5, 6, 8 ], [ 9, 10, 12 ], [13, 14, 16]); + + $B = Matrix([ [ 1, 2, 3 ], [ 4, 5, 6 ] ], [ [ 7, 8, 9 ], [ 10, 11, 12 ] ]); + $B->removeColumn(2); # returns Matrix([ [ 1, 2, 3 ] ], [ [ 7, 8, 9 ] ]); + +=cut + +sub removeColumn { + my ($self, $r) = @_; + my @dim = $self->dimensions; + my $degree = scalar @dim; + Value::Error("removeColumn cannot be used on a Matrix of degree 1") if $degree == 1; + Value::Error("cannot remove a Matrix's only column") if $dim[1] == 1; + my @indices = map { [ 1 .. $_ ] } @dim; + Value::Error("Can only remove columns 1 through $indices[1][-1]") + unless $r =~ /^\d+$/ && $r >= 1 && $r <= $indices[1][-1]; + return $self->subMatrix($indices[0], [ grep { $_ != $r } @{ $indices[1] } ], @indices[ 2 .. $#indices ]); +} + # @@@ assign @@@ # @@@ removeRow, removeColumn @@@ # @@@ Minor @@@ diff --git a/lib/Value/Real.pm b/lib/Value/Real.pm index ced0b4072a..a10407266a 100644 --- a/lib/Value/Real.pm +++ b/lib/Value/Real.pm @@ -101,7 +101,7 @@ sub power { my $x = $l->{data}[0]**$r->{data}[0]; return $self->inherit($other)->make($x) unless lc($x) eq 'nan' or lc($x) eq '-nan'; Value::Error("Can't raise a negative number to a non-integer power") if ($l->{data}[0] < 0); - Value::Error("Result of exponention is not a number"); + Value::Error("Result of exponentiation is not a number"); } sub modulo { diff --git a/lib/Value/Vector.pm b/lib/Value/Vector.pm index 94d95b9b03..68760e1127 100644 --- a/lib/Value/Vector.pm +++ b/lib/Value/Vector.pm @@ -55,7 +55,7 @@ sub new { } # -# Try to promote arbitary data to a vector +# Try to promote arbitrary data to a vector # sub promote { my $self = shift; diff --git a/lib/VectorField.pm b/lib/VectorField.pm index fbec4e9de6..278552d190 100644 --- a/lib/VectorField.pm +++ b/lib/VectorField.pm @@ -42,7 +42,7 @@ The graph_ref must come last. =item $vf = new VectorField ( dx_rule_ref, dy_rule_ref ); -A vector field object is created where the subroutines refered to by dx_rule_ref and dy_rule_ref define +A vector field object is created where the subroutines referred to by dx_rule_ref and dy_rule_ref define the x and y components of the vector field at (x,y). Both subroutines must be functions of two variables. =item $vf = new VectorField ( x_rule_ref, y_rule_ref, graph_ref ); @@ -52,7 +52,7 @@ of the vector field object is set to the domain of the graph. The graph_ref must =back -=head2 Properites +=head2 Properties All of the properties are set using the construction $new_value = $vf->property($new_value) and read using $current_value = $vf->property() diff --git a/lib/WWPlot.pm b/lib/WWPlot.pm index c3c98a2197..0b8c687a52 100644 --- a/lib/WWPlot.pm +++ b/lib/WWPlot.pm @@ -68,20 +68,20 @@ while @functions = $graph->fn(); -will give a list of the current functions (similary for labels and stamps). +will give a list of the current functions (similarly for labels and stamps). Either of the commands $graph->fn('reset'); $graph->fn('erase'); -will erase the array containing the functions and similary for the label and stamps arrays. +will erase the array containing the functions and similarly for the label and stamps arrays. =item h_axis, v_axis $h_axis_coordinate = $graph -> h_axis(); - $new_axis = $grpah -> h_axis($new_axis); + $new_axis = $graph -> h_axis($new_axis); Respectively read and set the vertical coordinate value in real world coordinates where the horizontal axis intersects the vertical one. The same construction reads and sets the coordinate @@ -100,7 +100,7 @@ $tick1, etc are the real world coordinate values for each of the tick marks. @h_grid = $graph -> h_grid(); @h_grid = $graph -> h_grid( $grid1, $grid2, $grid3, $grid4 ); -reads and sets the verical coordinates for the horizontal grid lines. The values +reads and sets the vertical coordinates for the horizontal grid lines. The values $grid1, etc are the real world coordinate values where the horizontal grid meets the vertical axis. diff --git a/lib/WeBWorK/PG.pm b/lib/WeBWorK/PG.pm index 594abb3a1b..9f78549fb9 100644 --- a/lib/WeBWorK/PG.pm +++ b/lib/WeBWorK/PG.pm @@ -115,7 +115,7 @@ sub new_helper ($invocant, %options) { translator => $translator, head_text => '', post_header_text => '', - body_text => "Unabled to read problem source file:\n$@\n", + body_text => "Unable to read problem source file:\n$@\n", answers => {}, result => {}, state => {}, @@ -160,19 +160,12 @@ sub new_helper ($invocant, %options) { ); } - # HTML_dpng uses an ImageGenerator. We have to render the queued equations. This must be done before the post - # processing, since the image tags output by the image generator initially include markers which are invalid html. - # Mojo::DOM will change these markers into attributes with values and this will fail. - if ($image_generator) { - $image_generator->render( - refresh => $options{refreshMath2img} // 0, - body_text => $translator->r_text, - ); - } - $translator->post_process_content if ref($translator->{rh_pgcore}) eq 'PGcore'; $translator->stringify_answers; + $image_generator->render(body_text => $translator->r_text, refresh => $options{refreshMath2img} // 0) + if $image_generator; + # Add the result summary set in post processing into the result. $result->{summary} = $translator->{rh_pgcore}{result_summary} if ref($translator->{rh_pgcore}) eq 'PGcore' @@ -361,7 +354,7 @@ sourceFilePath =item sourceFilePath (string) Location of the pg problem file to render. It must either be provided with an -absoute path, or a path relative to the given templateDirectory. +absolute path, or a path relative to the given templateDirectory. =item templateDirectory (string, default: '') @@ -609,7 +602,7 @@ These options are still used in some places in PG (mostly by macros that will eventually be deprecated and removed), but eventually that will all be fixed and these will no longer be needed. Note that webwork2 still needs to pass the courseName so the relevant course values (course html directories and urls) can -be optained from the WeBWorK::CourseEnvironment. +be obtained from the WeBWorK::CourseEnvironment. =back diff --git a/lib/WeBWorK/PG/Constants.pm b/lib/WeBWorK/PG/Constants.pm index 7e83e511b3..835687d9f1 100644 --- a/lib/WeBWorK/PG/Constants.pm +++ b/lib/WeBWorK/PG/Constants.pm @@ -11,7 +11,7 @@ use warnings; # ImageGenerator -# Arguments to pass to dvipng. This is dependant on the version of dvipng. +# Arguments to pass to dvipng. This is dependent on the version of dvipng. # # For dvipng versions 0.x # $ImageGenerator::DvipngArgs = "-x4000.5 -bgTransparent -Q6 -mode toshiba -D180"; diff --git a/lib/WeBWorK/PG/ConvertToPGML.pm b/lib/WeBWorK/PG/ConvertToPGML.pm index 04e4eeddba..d22d84420f 100644 --- a/lib/WeBWorK/PG/ConvertToPGML.pm +++ b/lib/WeBWorK/PG/ConvertToPGML.pm @@ -107,7 +107,7 @@ sub convertToPGML { while ($row && $row !~ /\);\s*$/) { $row = shift @rows; my @mrow = split(/#/, $row); - # This only adds the row if there is something relevent to the left of a # + # This only adds the row if there is something relevant to the left of a # $macros .= $mrow[0] if $mrow[0] !~ /^\s*$/; } # Split by commas and pull out the quotes. diff --git a/lib/WeBWorK/PG/Critic.pm b/lib/WeBWorK/PG/Critic.pm new file mode 100644 index 0000000000..0a656d494c --- /dev/null +++ b/lib/WeBWorK/PG/Critic.pm @@ -0,0 +1,94 @@ + +=head1 NAME + +WeBWorK::PG::Critic - Critique PG problem source code for best-practices. + +=head1 DESCRIPTION + +Analyze a pg file for use of old and current methods. + +=head1 FUNCTIONS + +=head2 critiquePGCode + + my @violations = critiquePGCode($code, $force = 0); + +Parses and critiques the given PG problem source provided in C<$code>. An array +of violations that are found is returned. Note that the elements of this return +array are L objects. The C method can be +called for each element, and that will either return a string or a reference to +a hash. The string return type will occur for a violation of a default +L policy. The last return type will occur with a +C policy, and the hash will contain a C key and +an C key containing the actual explanation. Note that the greater +the score, the worse the violation is. In some cases the C return +hash will also contain the key C which will be a reference to an +array each of whose entries will be a reference to a two element array whose +first element is the title of a sample problem and whose second element is the +path for that sample problem where the sample problem demonstrates a way to fix +the policy violation. + +Note that C<## no critic> annotations can be used in the code to disable a +violation for a line or the entire file. See L<"BENDING THE +RULES"|https://metacpan.org/pod/Perl::Critic#BENDING-THE-RULES>. However, if +C<$force> is true, then C<## no critic> annotations are ignored, and all +policies are enforced regardless. + +=head2 critiquePGFile + + my @violations = critiquePGFile($file, $force); + +This just executes C on the contents of C<$file> and returns +the violations found. +=cut + +package WeBWorK::PG::Critic; +use Mojo::Base 'Exporter', -signatures; + +use Mojo::File qw(path); +use PPI; +use Perl::Critic; + +require WeBWorK::PG::Translator; + +our @EXPORT_OK = qw(critiquePGFile critiquePGCode); + +sub critiquePGCode ($code, $force = 0) { + my $critic = Perl::Critic->new( + -severity => 4, + -exclude => [ + 'Perl::Critic::Policy::Modules::ProhibitMultiplePackages', + 'Perl::Critic::Policy::Modules::RequireEndWithOne', + 'Perl::Critic::Policy::Modules::RequireExplicitPackage', + 'Perl::Critic::Policy::Modules::RequireFilenameMatchesPackage', + 'Perl::Critic::Policy::Subroutines::ProhibitBuiltinHomonyms', + 'Perl::Critic::Policy::Subroutines::RequireArgUnpacking', + 'Perl::Critic::Policy::TestingAndDebugging::RequireUseStrict', + 'Perl::Critic::Policy::TestingAndDebugging::RequireUseWarnings', + 'Perl::Critic::Policy::Variables::RequireLocalizedPunctuationVars', + + # Make sure that Community and Freenode policies are not used if installed on the system. + '.*::Community::.*', '.*::Freenode::.*' + ], + -force => $force + ); + + my $translatedCode = WeBWorK::PG::Translator::default_preprocess_code($code); + + my $document = PPI::Document->new(\$translatedCode); + + # Provide the untranslated code so that policies can access it. It will be in the _doc key of the $document that is + # passed as the third argument to the violates method. See Perl::Critic::Policy::PG::ProhibitEnddocumentMatter which + # uses this for example. + $document->{untranslatedCode} = $code; + + return $critic->critique($document); +} + +sub critiquePGFile ($file, $force = 0) { + my $code = eval { path($file)->slurp('UTF-8') }; + die qq{Unable to read contents of "$file": $@} if $@; + return critiquePGCode($code, $force); +} + +1; diff --git a/lib/WeBWorK/PG/Critic/Utils.pm b/lib/WeBWorK/PG/Critic/Utils.pm new file mode 100644 index 0000000000..d983ec3dc1 --- /dev/null +++ b/lib/WeBWorK/PG/Critic/Utils.pm @@ -0,0 +1,153 @@ + +=head1 NAME + +WeBWorK::PG::Critic::Utils - Utility methods for PG Critic policies. + +=head1 DESCRIPTION + +Utility methods for PG Critic policies. + +=head1 FUNCTIONS + +=head2 getDeprecatedMacros + + my @deprecatedMacros = getDeprecatedMacros(); + +Returns a list of deprecated macros. These are the macros found in the +C directory. + +=head2 parsePGMLBlock + + my $pgmlElements = parsePGMLBlock(@lines); + +Parses the given C<@lines> of code from a PGML block and returns a reference to +a hash containing details of the PGML blocks found. + +If any C<[@ ... @]> blocks are found in the PGML code, then the return hash will +contain the key C which will be a reference to an array of +L objects representing the Perl code within the C<[@ ... @]> +blocks found. + +If the PGML content or a block within fails to parse, then the return hash will +contain the key C with a reference to an array of errors that occurred. + +Also if there are any warnings that occur in the parsing, those will be in the +C key of the return hash. + +=head2 parseTextBlock + + my $textElements = parseTextBlock(@lines); + +Parses the given C<@lines> of code from a C/C, +C/C, or C/C block and +returns a reference to a hash containing details of the elements found. + +If any C<\{ ... \}> blocks are found in the code, then the return hash will +contain the key C which will be a reference to an array of +L objects representing the Perl code within the C<\{ ... \}> +blocks found. + +If a block within fails to parse, then the return hash will contain the key +C which is a reference to an array of errors that occurred. + +=cut + +package WeBWorK::PG::Critic::Utils; +use Mojo::Base 'Exporter', -signatures; + +use Mojo::File qw(curfile path); +use PPI; +use Perl::Critic::Utils qw(:classification :ppi); +use Scalar::Util qw(blessed); +use Mojo::Util qw(md5_sum encode); +use Env qw(PG_ROOT); + +use lib curfile->dirname->dirname->dirname->dirname->dirname->child('lib')->to_string; + +require Value; + +our @EXPORT_OK = qw(getDeprecatedMacros parsePGMLBlock parseTextBlock); + +$PG_ROOT = curfile->dirname->dirname->dirname->dirname->dirname; + +sub getDeprecatedMacros () { + state $deprecatedMacros; + return $deprecatedMacros if $deprecatedMacros; + return $deprecatedMacros = + { map { $_->basename => 1 } @{ path($PG_ROOT)->child('macros', 'deprecated')->list } }; +} + +# Mock methods used by PGML. +sub main::PG_restricted_eval ($code) { return $code; } +sub main::loadMacros(@macros) { return; } +sub main::Context() { return; } + +do "$PG_ROOT/macros/core/PGML.pl"; + +sub walkPGMLTree ($block, $results) { + for my $item (@{ $block->{stack} }) { + next unless blessed $item && $item->isa('PGML::Block'); + if ($item->{type} eq 'command') { + my $command = PPI::Document->new(\($item->{text})); + if ($command->errstr) { + push(@{ $results->{errors} }, $command->errstr); + } else { + push(@{ $results->{commands} }, $command); + } + } + walkPGMLTree($item, $results); + } + return $results; +} + +# For now, only command blocks are returned. Add other PGML elements as needed. +sub parsePGMLBlock (@lines) { + state %processedBlocks; + + my $source = join('', @lines); + + # Cache the results of parsing particular PGML blocks so that if multiple policies + # use the same PGML block the parsing does not need to be done again. + my $sourceHash = md5_sum(encode('UTF-8', $source)); + return $processedBlocks{$sourceHash} if defined $processedBlocks{$sourceHash}; + + PGML::ClearWarnings(); + my $parser = eval { PGML::Parse->new($source =~ s/\\\\/\\/gr) }; + return { errors => [$@], warnings => \@PGML::warnings } if $@; + + return $processedBlocks{$sourceHash} = + WeBWorK::PG::Critic::Utils::walkPGMLTree($parser->{root}, { warnings => \@PGML::warnings }); +} + +# For now, only contents of \{ .. \} blocks are returned. Add other text elements as needed. +sub parseTextBlock (@lines) { + state %processedBlocks; + + my $source = join('', @lines); + + # Cache the results of parsing particular text blocks so that if multiple policies + # use the same text block the parsing does not need to be done again. + my $sourceHash = md5_sum(encode('UTF-8', $source)); + return $processedBlocks{$sourceHash} if defined $processedBlocks{$sourceHash}; + + my $results = {}; + + while ($source ne '') { + if ($source =~ /\Q\\{\E/s) { + $source =~ s/^(.*?)\Q\\{\E//s; + $source =~ s/^(.*?)\Q\\}\E//s; + my $command = PPI::Document->new(\($1)); + if ($command->errstr) { + push(@{ $results->{errors} }, $command->errstr); + } else { + push(@{ $results->{commands} }, $command); + } + } else { + last; + } + + } + + return $processedBlocks{$sourceHash} = $results; +} +1; diff --git a/lib/WeBWorK/PG/Environment.pm b/lib/WeBWorK/PG/Environment.pm index 98ae5991c2..bf0f7ef37a 100644 --- a/lib/WeBWorK/PG/Environment.pm +++ b/lib/WeBWorK/PG/Environment.pm @@ -17,7 +17,7 @@ The configuration is initially loaded from $ENV{PG_ROOT}/conf/pg_config.dist.yml. If it is desired to change the default values, then copy the $ENV{PG_ROOT}/conf/pg_config.dist.yml to $ENV{PG_ROOT}/conf/pg_config.yml, and make changes in the copy. The default -values will be overriden with the changed values in the copy. Note that invalid +values will be overridden with the changed values in the copy. Note that invalid values added to the copy will cause a warning to be issued. If the WeBWorK::CourseEnvironment module is found, then the configuration @@ -65,7 +65,7 @@ sub new ($invocant, $courseName = '___') { # Override pg settings and things needed by WeBWorK::PG::IO with settings from the course environment. Pick and # choose the important values from the webwork2 course environment. Some values in the PG configuration are not - # overriden. These are the values that are used in the WeBWorK::PG and WeBWorK::PG::IO. Note that in WeBWorK::PG + # overridden. These are the values that are used in the WeBWorK::PG and WeBWorK::PG::IO. Note that in WeBWorK::PG # most values for the translator environment are taken from the options passed in, and the values in the pg # environment are used for the default values. if (defined $ce) { diff --git a/lib/WeBWorK/PG/EquationCache.pm b/lib/WeBWorK/PG/EquationCache.pm index ffc19f9eca..f997a7dafb 100644 --- a/lib/WeBWorK/PG/EquationCache.pm +++ b/lib/WeBWorK/PG/EquationCache.pm @@ -13,7 +13,7 @@ WeBWorK::PG::EquationCache - create and cache images of TeX equations. WeBWorK::PG::EquationCache maintains a list of unique identifiers for TeX strings. The unique identifier is based on an MD5 hash of the TeX string, and a -sequence number. Before calcuating the MD5 hash of a TeX string, all whitespace +sequence number. Before calculating the MD5 hash of a TeX string, all whitespace is removed. =head2 FILE FORMAT diff --git a/lib/WeBWorK/PG/IO.pm b/lib/WeBWorK/PG/IO.pm index dc27f96287..5d9098dd20 100644 --- a/lib/WeBWorK/PG/IO.pm +++ b/lib/WeBWorK/PG/IO.pm @@ -38,9 +38,8 @@ our $pg_envir; =head2 includePGtext -This is used in processing some of the sample CAPA files and in creating aliases -to redirect calls to duplicate problems so that they go to the original problem -instead. It is called by includePGproblem. +This is used in creating aliases to redirect calls to duplicate problems so that +they go to the original problem instead. It is called by includePGproblem. Usage: C @@ -77,9 +76,8 @@ Read the contents of a pg file. Usage: C Don't use for huge files. The file name will have .pg appended to it if it -doesn't already end in .pg. This is used in importing additional .pg files as -is done in the sample problems translated from CAPA. Returns a reference to a -string containing the contents of the file. +doesn't already end in .pg. This is used in importing additional .pg files. +Returns a reference to a string containing the contents of the file. =cut @@ -317,7 +315,7 @@ sub externalCommand { # Isolate the call to the sage server in case we have to jazz it up. sub query_sage_server { - my ($python, $url, $accepted_tos, $setSeed, $webworkfunc, $debug, $curlCommand) = @_; + my ($python, $url, $setSeed, $webworkfunc, $debug, $curlCommand) = @_; # Validate url to prevent shell injection — must look like a URL. if ($url && $url !~ m{^https?://[^\s;|&`\$]+$}) { @@ -330,7 +328,7 @@ sub query_sage_server { $curlCommand, '-i', '-k', '-sS', '-L', '--data-urlencode', - "accepted_tos=$accepted_tos", '--data-urlencode', + "accepted_tos=true", '--data-urlencode', 'user_expressions={"WEBWORK":"_webwork_safe_json(WEBWORK)"}', '--data-urlencode', "code=${setSeed}${webworkfunc}$python", $url // (), ); @@ -402,8 +400,7 @@ sub query_sage_server { # Success! Put any extraneous splits back together. $result = join("\r\n\r\n", @lines); } else { - warn "ERROR in contacting sage server. Did you accept the terms of service by " - . "setting { accepted_tos => 'true' } in the askSage options?\n$content\n"; + warn "ERROR in contacting sage server.\n$content\n"; $result = undef; } @@ -423,44 +420,44 @@ sub AskSage { chomp($python); # To send values back in a hash, add them to the python WEBWORK dictionary. - my $url = $args->{url} || 'https://sagecell.sagemath.org/service'; - my $seed = $args->{seed}; - my $accepted_tos = $args->{accepted_tos} || 'false'; # Force author to accept terms of service explicitly. - my $debug = $args->{debug} || 0; - my $setSeed = $seed ? "set_random_seed($seed)\n" : ''; - my $curlCommand = $args->{curlCommand}; + my $url = $args->{url} || $pg_envir->{URLs}{sagecellService} || 'https://sagecell.sagemath.org/service'; + my $seed = $args->{seed}; + my $debug = $args->{debug} || 0; + my $setSeed = $seed ? "set_random_seed($seed)\n" : ''; + my $curlCommand = $args->{curlCommand}; my $webworkfunc = < 0 }; # We want to export more than one piece of information. eval { - my $output = query_sage_server($python, $url, $accepted_tos, $setSeed, $webworkfunc, $debug, $curlCommand); + my $output = query_sage_server($python, $url, $setSeed, $webworkfunc, $debug, $curlCommand); # has something been returned? not_null($output) or die "Unable to make a sage call to $url."; @@ -488,7 +485,7 @@ END warn "now success is $success because status was ok" if $debug; if ($success) { my $WEBWORK_variable_non_empty = 0; - my $sage_WEBWORK_data = $decoded->{execute_reply}{user_expressions}{WEBWORK}{data}{'text/plain'}; + my $sage_WEBWORK_data = $decoded->{execute_reply}{user_expressions}{WEBWORK}{data}{'text/plain'} // ''; warn "sage_WEBWORK_data $sage_WEBWORK_data" if $debug; if (not_null($sage_WEBWORK_data)) { $WEBWORK_variable_non_empty = # another hack because '{}' is sometimes returned diff --git a/lib/WeBWorK/PG/ImageGenerator.pm b/lib/WeBWorK/PG/ImageGenerator.pm index a5d09c86b3..d30424af26 100644 --- a/lib/WeBWorK/PG/ImageGenerator.pm +++ b/lib/WeBWorK/PG/ImageGenerator.pm @@ -250,8 +250,8 @@ sub add ($self, $string, $mode = 'inline') { # Determine what the image's "number" is. if ($useCache) { $imageNum = $self->{equationCache}->lookup($realString); - $aligntag = 'MaRkEr' . $imageNum if $self->{useMarkers}; - $depths->{$imageNum} = 'none' if $self->{store_depths}; + $aligntag = qq{data-imagegen-alignment-marker="$imageNum"} if $self->{useMarkers}; + $depths->{$imageNum} = 'none' if $self->{store_depths}; # Insert a slash after 2 characters. This effectively divides the images into 16^2 = 256 subdirectories. substr($imageNum, 2, 0) = '/'; } else { @@ -461,11 +461,16 @@ sub fix_markers ($self) { my %depths = %{ $self->{depths} }; for my $depthkey (keys %depths) { + # The data-imagegen-alignment-marker value may be quoted with double quotes or with " if the image is + # inside another HTML element attribute (such as for images in the feedback button). So both quote types need + # to be checked, and the replaced style attribute needs to use the same quoting that it comes in with. if ($depths{$depthkey} eq 'none') { - ${ $self->{body_text} } =~ s/MaRkEr$depthkey/style="vertical-align:$self->{dvipng_align}"/g; + ${ $self->{body_text} } =~ + s/data-imagegen-alignment-marker=("|")$depthkey\1/style=$1vertical-align:$self->{dvipng_align}$1/g; } else { my $ndepth = 0 - $depths{$depthkey}; - ${ $self->{body_text} } =~ s/MaRkEr$depthkey/style="vertical-align:${ndepth}px"/g; + ${ $self->{body_text} } =~ + s/data-imagegen-alignment-marker=("|")$depthkey\1/style=$1vertical-align:${ndepth}px$1/g; } } return; diff --git a/lib/WeBWorK/PG/Localize/cs-CZ.po b/lib/WeBWorK/PG/Localize/cs-CZ.po index 00ff07110e..d15a85bdcb 100644 --- a/lib/WeBWorK/PG/Localize/cs-CZ.po +++ b/lib/WeBWorK/PG/Localize/cs-CZ.po @@ -2,83 +2,153 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# # Translators: # Glenn Rice, 2023 # Robert Mařík , 2024 -# +# msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: 2023-11-19 04:33+0000\n" "Last-Translator: Robert Mařík , 2024\n" -"Language-Team: Czech (Czech Republic) (https://app.transifex.com/webwork/teams/16644/cs_CZ/)\n" +"Language-Team: Czech (Czech Republic) (https://app.transifex.com/webwork/" +"teams/16644/cs_CZ/)\n" +"Language: cs_CZ\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: cs_CZ\n" -"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" +"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n " +"<= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" + +#. ($edgeText[0], $edgeText[1]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:813 +msgid " There are edges between %1 and %2." +msgstr "" + +#. (join($comma, @edgeText[ 0 .. $#edgeText - 1 ]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:817 +msgid " There are edges between %1%2and %3." +msgstr "" + +#. ($edgeText[0]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:811 +msgid " There is an edge between %1." +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:806 +msgid "%1 and %2" +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:803 +msgid "%1 and %2 with weight %3" +msgstr "" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1455 +#: /opt/webwork/pg/macros/PG.pl:1470 msgid "%1 of the answers %plural(%1,has,have) been graded." msgstr "" #. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1427 +#: /opt/webwork/pg/macros/PG.pl:1442 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1451 +#: /opt/webwork/pg/macros/PG.pl:1466 msgid "%1 of the answers will be graded later." msgstr "" #. ($options{resultTitle}) -#: /opt/webwork/pg/macros/PG.pl:1224 +#: /opt/webwork/pg/macros/PG.pl:1239 msgid "%1 with message" msgstr "" #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1117 +#: /opt/webwork/pg/macros/PG.pl:1119 msgid "%1% correct" msgstr "%1% správně" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1440 -msgid "" -"%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "Nebylo odpovězeno na %quant(%1,otázku,otázky)." -#: /opt/webwork/pg/macros/PG.pl:1414 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:596 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:793 +msgid ", " +msgstr "" + +#. ($self->labelsString) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:791 +msgid "A graph with vertices %1." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:221 +msgid "Add Bucket" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1429 msgid "All of the answers are correct." msgstr "Všechny odpovědi jsou správně." -#: /opt/webwork/pg/macros/PG.pl:1405 +#: /opt/webwork/pg/macros/PG.pl:1420 msgid "All of the computer gradable answers are correct." msgstr "Všechny odpovědí opravované počítačem jsou správně." -#: /opt/webwork/pg/macros/PG.pl:1060 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1323 +msgid "An edge traversed by this path does not exist in the graph." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1062 msgid "Answer Preview" msgstr "Náhled odpovědi" -#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +#: /opt/webwork/pg/macros/PG.pl:1255 /opt/webwork/pg/macros/PG.pl:1331 msgid "Close" msgstr "Zavřít" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +#: /opt/webwork/pg/lib/DragNDrop.pm:233 +msgid "Close Help" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2908 msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1109 +#: /opt/webwork/pg/macros/PG.pl:1111 msgid "Correct" msgstr "Správně" -#: /opt/webwork/pg/macros/PG.pl:1322 +#: /opt/webwork/pg/macros/PG.pl:1337 msgid "Correct Answer" msgstr "Správná odpověď" +#: /opt/webwork/pg/lib/DragNDrop.pm:232 +msgid "Drag and Drop Help" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:241 +msgid "" +"Drag items in the universal set to copy them to a list. Tab and shift-tab " +"can be used to focus universal set items. A focused item in the universal " +"set can be added to the first list with the right or down arrow keys, or " +"added to the last list with the left or up arrow keys. A focused item in a " +"list can be removed by using the left or right arrow key until it returns to " +"the universal set." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:235 +msgid "" +"Drag to reorganize items within lists or to move items to a different list. " +"Tab and shift-tab can be used to focus list items. The left and right arrow " +"keys move a focused list item to the list to the left or right. The up and " +"down arrow keys move a focused list item up and down inside a list." +msgstr "" + #: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" @@ -98,7 +168,7 @@ msgstr "" msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:64 msgid "Graded" msgstr "Vyhodnoceno" @@ -106,11 +176,11 @@ msgstr "Vyhodnoceno" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1305 msgid "Hint" msgstr "Nápověda" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1304 msgid "Hint:" msgstr "Nápověda:" @@ -118,20 +188,40 @@ msgstr "Nápověda:" msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1113 +#: /opt/webwork/pg/macros/PG.pl:1115 /opt/webwork/pg/macros/PG.pl:1132 msgid "Incorrect" msgstr "Nesprávně" +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:227 +msgid "Item %1 in the universal set added as item %2 to list %3." +msgstr "" + +#. ('%1s', '%2s') +#: /opt/webwork/pg/lib/DragNDrop.pm:228 +msgid "Item %1 removed from list %2." +msgstr "" + +#. ('%1s', '%2s', '%3s', '%4s') +#: /opt/webwork/pg/lib/DragNDrop.pm:231 +msgid "Moved item %1 in list %2 to item %3 in list %4." +msgstr "" + +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:229 +msgid "Moved item %1 in list %2 to item %3." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "Poznámka:" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:139 msgid "Preview" msgstr "Náhled" -#: /opt/webwork/pg/macros/PG.pl:1285 +#: /opt/webwork/pg/macros/PG.pl:1300 msgid "Preview of Your Answer" msgstr "Náhled odpovědi" @@ -140,7 +230,19 @@ msgstr "Náhled odpovědi" msgid "Quick Entry" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1328 +#: /opt/webwork/pg/lib/DragNDrop.pm:222 +msgid "Remove" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:220 +msgid "Reset" +msgstr "" + +#: /opt/webwork/pg/macros/graph/AppletObjects.pl:115 +msgid "Return this question to its initial state" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1343 msgid "Reveal" msgstr "" @@ -148,11 +250,11 @@ msgstr "" msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1297 msgid "Solution" msgstr "Řešení" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1296 msgid "Solution:" msgstr "Řešení:" @@ -160,40 +262,69 @@ msgstr "Řešení:" msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1375 +#: /opt/webwork/pg/macros/PG.pl:1390 msgid "The answer has been graded." msgstr "Správnost odpovědi nebyla vyhodnocena." -#: /opt/webwork/pg/macros/PG.pl:1393 +#: /opt/webwork/pg/macros/PG.pl:1408 msgid "The answer is NOT correct." msgstr "Odpověď NENÍ správně." -#: /opt/webwork/pg/macros/PG.pl:1364 +#: /opt/webwork/pg/macros/PG.pl:1379 msgid "The answer is correct." msgstr "Odpověď je správně." -#: /opt/webwork/pg/macros/PG.pl:1374 +#: /opt/webwork/pg/macros/PG.pl:1389 msgid "The answer will be graded later." msgstr "Odpověď bude vyhodnocena později." -#: /opt/webwork/pg/macros/PG.pl:1384 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1284 +msgid "The degrees of the vertices in this graph are not all even." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1399 msgid "The question has not been answered." msgstr "Otázka nebyla zodpovězena." -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1110 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1671 msgid "This answer was marked correct because the primary answer is correct." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:85 msgid "This answer will be graded at a later time." msgstr "Odpověď bude vyhodnocena později." +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1307 +msgid "" +"This graph does not have two vertices of odd degree and all other vertices " +"of even degree." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1376 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1383 +msgid "This graph has a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1281 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1294 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1377 +msgid "This graph is not connected." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1331 +msgid "This path does not traverse all edges." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1343 +msgid "This path is not a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3099 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -205,19 +336,22 @@ msgstr "" msgid "True" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:62 msgid "Ungraded" msgstr "Zatím nehodnoceno" -#: /opt/webwork/pg/macros/PG.pl:1280 +#: /opt/webwork/pg/lib/DragNDrop.pm:225 +msgid "Universal Set" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1128 +msgid "Unknown" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1295 msgid "You Entered" msgstr "Zadaná odpověď" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 -msgid "You can earn partial credit on this problem." -msgstr "Za částečné zodpovězení této úlohy můžete obdržet částečné hodnocení." - #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." msgstr "" @@ -226,7 +360,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3092 msgid "Your browser does not support the video tag." msgstr "" @@ -234,71 +368,71 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#. ($name) #. ($1) +#. ($name) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:650 msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2977 msgid "end image description" msgstr "" +# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -# does not need to be translated #: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 #: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:818 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2893 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2975 msgid "image description" msgstr "" +#. ($i + 1) #. (1) #. ($count) -#. ($i + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:558 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:567 msgid "option %1 " msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:821 msgid "otherwise" msgstr "" -#. ($2 + 1) #. ($radioIndex) +#. ($2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:652 msgid "part %1 " msgstr "" #. ($1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:645 msgid "problem %1 " msgstr "" #. ($1 + 1, $2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:658 msgid "row %1 column %2 " msgstr "" #. ($partIndex) -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:653 msgid "subpart %1 " msgstr "" diff --git a/lib/WeBWorK/PG/Localize/de.po b/lib/WeBWorK/PG/Localize/de.po index ebf500339c..f40f97c465 100644 --- a/lib/WeBWorK/PG/Localize/de.po +++ b/lib/WeBWorK/PG/Localize/de.po @@ -2,10 +2,10 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# # Translators: # Glenn Rice, 2023 -# +# msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" @@ -13,71 +13,139 @@ msgstr "" "PO-Revision-Date: 2023-11-19 04:33+0000\n" "Last-Translator: Glenn Rice, 2023\n" "Language-Team: German (https://app.transifex.com/webwork/teams/16644/de/)\n" +"Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +#. ($edgeText[0], $edgeText[1]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:813 +msgid " There are edges between %1 and %2." +msgstr "" + +#. (join($comma, @edgeText[ 0 .. $#edgeText - 1 ]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:817 +msgid " There are edges between %1%2and %3." +msgstr "" + +#. ($edgeText[0]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:811 +msgid " There is an edge between %1." +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:806 +msgid "%1 and %2" +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:803 +msgid "%1 and %2 with weight %3" +msgstr "" + #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1455 +#: /opt/webwork/pg/macros/PG.pl:1470 msgid "%1 of the answers %plural(%1,has,have) been graded." msgstr "" #. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1427 +#: /opt/webwork/pg/macros/PG.pl:1442 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1451 +#: /opt/webwork/pg/macros/PG.pl:1466 msgid "%1 of the answers will be graded later." msgstr "" #. ($options{resultTitle}) -#: /opt/webwork/pg/macros/PG.pl:1224 +#: /opt/webwork/pg/macros/PG.pl:1239 msgid "%1 with message" msgstr "" #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1117 +#: /opt/webwork/pg/macros/PG.pl:1119 msgid "%1% correct" msgstr "%1% richtig" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1440 -msgid "" -"%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "%quant(%1,Frage wurde,Fragen wurden) nicht beantwortet." -#: /opt/webwork/pg/macros/PG.pl:1414 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:596 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:793 +msgid ", " +msgstr "" + +#. ($self->labelsString) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:791 +msgid "A graph with vertices %1." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:221 +msgid "Add Bucket" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1429 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1405 +#: /opt/webwork/pg/macros/PG.pl:1420 msgid "All of the computer gradable answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1060 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1323 +msgid "An edge traversed by this path does not exist in the graph." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1062 msgid "Answer Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +#: /opt/webwork/pg/macros/PG.pl:1255 /opt/webwork/pg/macros/PG.pl:1331 msgid "Close" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +#: /opt/webwork/pg/lib/DragNDrop.pm:233 +msgid "Close Help" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2908 msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1109 +#: /opt/webwork/pg/macros/PG.pl:1111 msgid "Correct" msgstr "Richtig" -#: /opt/webwork/pg/macros/PG.pl:1322 +#: /opt/webwork/pg/macros/PG.pl:1337 msgid "Correct Answer" msgstr "Lösung" +#: /opt/webwork/pg/lib/DragNDrop.pm:232 +msgid "Drag and Drop Help" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:241 +msgid "" +"Drag items in the universal set to copy them to a list. Tab and shift-tab " +"can be used to focus universal set items. A focused item in the universal " +"set can be added to the first list with the right or down arrow keys, or " +"added to the last list with the left or up arrow keys. A focused item in a " +"list can be removed by using the left or right arrow key until it returns to " +"the universal set." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:235 +msgid "" +"Drag to reorganize items within lists or to move items to a different list. " +"Tab and shift-tab can be used to focus list items. The left and right arrow " +"keys move a focused list item to the list to the left or right. The up and " +"down arrow keys move a focused list item up and down inside a list." +msgstr "" + #: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" @@ -97,7 +165,7 @@ msgstr "" msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:64 msgid "Graded" msgstr "" @@ -105,11 +173,11 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1305 msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1304 msgid "Hint:" msgstr "Hinweis: " @@ -117,20 +185,40 @@ msgstr "Hinweis: " msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1113 +#: /opt/webwork/pg/macros/PG.pl:1115 /opt/webwork/pg/macros/PG.pl:1132 msgid "Incorrect" msgstr "Falsch" +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:227 +msgid "Item %1 in the universal set added as item %2 to list %3." +msgstr "" + +#. ('%1s', '%2s') +#: /opt/webwork/pg/lib/DragNDrop.pm:228 +msgid "Item %1 removed from list %2." +msgstr "" + +#. ('%1s', '%2s', '%3s', '%4s') +#: /opt/webwork/pg/lib/DragNDrop.pm:231 +msgid "Moved item %1 in list %2 to item %3 in list %4." +msgstr "" + +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:229 +msgid "Moved item %1 in list %2 to item %3." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "Note:" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:139 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1285 +#: /opt/webwork/pg/macros/PG.pl:1300 msgid "Preview of Your Answer" msgstr "" @@ -139,7 +227,19 @@ msgstr "" msgid "Quick Entry" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1328 +#: /opt/webwork/pg/lib/DragNDrop.pm:222 +msgid "Remove" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:220 +msgid "Reset" +msgstr "" + +#: /opt/webwork/pg/macros/graph/AppletObjects.pl:115 +msgid "Return this question to its initial state" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1343 msgid "Reveal" msgstr "" @@ -147,11 +247,11 @@ msgstr "" msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1297 msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1296 msgid "Solution:" msgstr "Lösung:" @@ -159,40 +259,69 @@ msgstr "Lösung:" msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1375 +#: /opt/webwork/pg/macros/PG.pl:1390 msgid "The answer has been graded." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1393 +#: /opt/webwork/pg/macros/PG.pl:1408 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1364 +#: /opt/webwork/pg/macros/PG.pl:1379 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1374 +#: /opt/webwork/pg/macros/PG.pl:1389 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1384 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1284 +msgid "The degrees of the vertices in this graph are not all even." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1399 msgid "The question has not been answered." msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1110 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1671 msgid "This answer was marked correct because the primary answer is correct." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:85 msgid "This answer will be graded at a later time." msgstr "" +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1307 +msgid "" +"This graph does not have two vertices of odd degree and all other vertices " +"of even degree." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1376 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1383 +msgid "This graph has a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1281 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1294 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1377 +msgid "This graph is not connected." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "Dies is eine neu randomisierte Version der Aufgabe." -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1331 +msgid "This path does not traverse all edges." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1343 +msgid "This path is not a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3099 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -204,18 +333,21 @@ msgstr "Diese Aufgabe besteht aus mehreren Teilen." msgid "True" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:62 msgid "Ungraded" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1280 -msgid "You Entered" +#: /opt/webwork/pg/lib/DragNDrop.pm:225 +msgid "Universal Set" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1128 +msgid "Unknown" msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 -msgid "You can earn partial credit on this problem." -msgstr "Für diese Aufgabe können Sie Teilpunkte erhalten." +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "You Entered" +msgstr "" #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." @@ -227,7 +359,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "Antworten können im nächsten Teil nicht mehr geändert werden!" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3092 msgid "Your browser does not support the video tag." msgstr "" @@ -235,71 +367,71 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#. ($name) #. ($1) +#. ($name) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:650 msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2977 msgid "end image description" msgstr "" +# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -# does not need to be translated #: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 #: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:818 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2893 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2975 msgid "image description" msgstr "" +#. ($i + 1) #. (1) #. ($count) -#. ($i + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:558 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:567 msgid "option %1 " msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:821 msgid "otherwise" msgstr "" -#. ($2 + 1) #. ($radioIndex) +#. ($2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:652 msgid "part %1 " msgstr "" #. ($1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:645 msgid "problem %1 " msgstr "" #. ($1 + 1, $2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:658 msgid "row %1 column %2 " msgstr "" #. ($partIndex) -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:653 msgid "subpart %1 " msgstr "" diff --git a/lib/WeBWorK/PG/Localize/el.po b/lib/WeBWorK/PG/Localize/el.po index 6524aa5547..9d46a6f4e3 100644 --- a/lib/WeBWorK/PG/Localize/el.po +++ b/lib/WeBWorK/PG/Localize/el.po @@ -2,11 +2,11 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# # Translators: # Glenn Rice, 2023 # Ioannis Souldatos, 2023 -# +# msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" @@ -14,72 +14,140 @@ msgstr "" "PO-Revision-Date: 2023-11-19 04:33+0000\n" "Last-Translator: Ioannis Souldatos, 2023\n" "Language-Team: Greek (https://app.transifex.com/webwork/teams/16644/el/)\n" +"Language: el\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: el\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +#. ($edgeText[0], $edgeText[1]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:813 +msgid " There are edges between %1 and %2." +msgstr "" + +#. (join($comma, @edgeText[ 0 .. $#edgeText - 1 ]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:817 +msgid " There are edges between %1%2and %3." +msgstr "" + +#. ($edgeText[0]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:811 +msgid " There is an edge between %1." +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:806 +msgid "%1 and %2" +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:803 +msgid "%1 and %2 with weight %3" +msgstr "" + #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1455 +#: /opt/webwork/pg/macros/PG.pl:1470 msgid "%1 of the answers %plural(%1,has,have) been graded." msgstr "" #. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1427 +#: /opt/webwork/pg/macros/PG.pl:1442 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "%1 από τις απαντήσεις ΔΕΝ είναι %plural(%1,σωστή, σωστές)." #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1451 +#: /opt/webwork/pg/macros/PG.pl:1466 msgid "%1 of the answers will be graded later." msgstr "%1 από τις απαντήσεις θα βαθμολογηθούν αργότερα. " #. ($options{resultTitle}) -#: /opt/webwork/pg/macros/PG.pl:1224 +#: /opt/webwork/pg/macros/PG.pl:1239 msgid "%1 with message" msgstr "" #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1117 +#: /opt/webwork/pg/macros/PG.pl:1119 msgid "%1% correct" msgstr "%1% σωστό" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1440 -msgid "" -"%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "" "%quant(%1,των ερωτήσεων απομένει, των ερωτήσεων απομένουν) αναπάντητες." -#: /opt/webwork/pg/macros/PG.pl:1414 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:596 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:793 +msgid ", " +msgstr "" + +#. ($self->labelsString) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:791 +msgid "A graph with vertices %1." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:221 +msgid "Add Bucket" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1429 msgid "All of the answers are correct." msgstr "Όλες οι απαντήσεις είναι σωστές." -#: /opt/webwork/pg/macros/PG.pl:1405 +#: /opt/webwork/pg/macros/PG.pl:1420 msgid "All of the computer gradable answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1060 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1323 +msgid "An edge traversed by this path does not exist in the graph." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1062 msgid "Answer Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +#: /opt/webwork/pg/macros/PG.pl:1255 /opt/webwork/pg/macros/PG.pl:1331 msgid "Close" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +#: /opt/webwork/pg/lib/DragNDrop.pm:233 +msgid "Close Help" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2908 msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1109 +#: /opt/webwork/pg/macros/PG.pl:1111 msgid "Correct" msgstr "Σωστό" -#: /opt/webwork/pg/macros/PG.pl:1322 +#: /opt/webwork/pg/macros/PG.pl:1337 msgid "Correct Answer" msgstr "Σωστή Απάντηση" +#: /opt/webwork/pg/lib/DragNDrop.pm:232 +msgid "Drag and Drop Help" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:241 +msgid "" +"Drag items in the universal set to copy them to a list. Tab and shift-tab " +"can be used to focus universal set items. A focused item in the universal " +"set can be added to the first list with the right or down arrow keys, or " +"added to the last list with the left or up arrow keys. A focused item in a " +"list can be removed by using the left or right arrow key until it returns to " +"the universal set." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:235 +msgid "" +"Drag to reorganize items within lists or to move items to a different list. " +"Tab and shift-tab can be used to focus list items. The left and right arrow " +"keys move a focused list item to the list to the left or right. The up and " +"down arrow keys move a focused list item up and down inside a list." +msgstr "" + #: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "Λάθος" @@ -99,7 +167,7 @@ msgstr "Επιστροφή σε Μέρος 1" msgid "Go on to next part" msgstr "Μετάβαση στο επόμενο" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:64 msgid "Graded" msgstr "" @@ -107,33 +175,52 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "Στην έντυπη μορφή εμφανίζεται πάντα η αρχική μορφή του προβλήματος." -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1305 msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1304 msgid "Hint:" msgstr "Υπόδειξη:" #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:411 msgid "If you come back to it later, it may revert to its original version." -msgstr "" -"Αν επιστρέψετε αργότερα, ενδέχεται να επανέλθει στην αρχική του μορφή." +msgstr "Αν επιστρέψετε αργότερα, ενδέχεται να επανέλθει στην αρχική του μορφή." -#: /opt/webwork/pg/macros/PG.pl:1113 +#: /opt/webwork/pg/macros/PG.pl:1115 /opt/webwork/pg/macros/PG.pl:1132 msgid "Incorrect" msgstr "Λάθος" +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:227 +msgid "Item %1 in the universal set added as item %2 to list %3." +msgstr "" + +#. ('%1s', '%2s') +#: /opt/webwork/pg/lib/DragNDrop.pm:228 +msgid "Item %1 removed from list %2." +msgstr "" + +#. ('%1s', '%2s', '%3s', '%4s') +#: /opt/webwork/pg/lib/DragNDrop.pm:231 +msgid "Moved item %1 in list %2 to item %3 in list %4." +msgstr "" + +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:229 +msgid "Moved item %1 in list %2 to item %3." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "Σημείωση:" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:139 msgid "Preview" msgstr "Προεπισκόπηση" -#: /opt/webwork/pg/macros/PG.pl:1285 +#: /opt/webwork/pg/macros/PG.pl:1300 msgid "Preview of Your Answer" msgstr "Προεπισκόπηση απάντησης" @@ -142,7 +229,19 @@ msgstr "Προεπισκόπηση απάντησης" msgid "Quick Entry" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1328 +#: /opt/webwork/pg/lib/DragNDrop.pm:222 +msgid "Remove" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:220 +msgid "Reset" +msgstr "" + +#: /opt/webwork/pg/macros/graph/AppletObjects.pl:115 +msgid "Return this question to its initial state" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1343 msgid "Reveal" msgstr "" @@ -150,11 +249,11 @@ msgstr "" msgid "Set random seed to:" msgstr "Ορισμός τυχαίου αριθμού:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1297 msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1296 msgid "Solution:" msgstr "Απάντηση:" @@ -162,40 +261,69 @@ msgstr "Απάντηση:" msgid "Submit your answers again to go on to the next part." msgstr "Υποβάλετε τις απαντήσεις σας ξανά για να συνεχίσετε." -#: /opt/webwork/pg/macros/PG.pl:1375 +#: /opt/webwork/pg/macros/PG.pl:1390 msgid "The answer has been graded." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1393 +#: /opt/webwork/pg/macros/PG.pl:1408 msgid "The answer is NOT correct." msgstr "Η απάντηση ΔΕΝ είναι σωστή." -#: /opt/webwork/pg/macros/PG.pl:1364 +#: /opt/webwork/pg/macros/PG.pl:1379 msgid "The answer is correct." msgstr "Η απάντηση είναι σωστή!" -#: /opt/webwork/pg/macros/PG.pl:1374 +#: /opt/webwork/pg/macros/PG.pl:1389 msgid "The answer will be graded later." msgstr "Η απάντηση θα βαθμολογηθεί αργότερα." -#: /opt/webwork/pg/macros/PG.pl:1384 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1284 +msgid "The degrees of the vertices in this graph are not all even." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1399 msgid "The question has not been answered." msgstr "Η ερώτηση δεν έχει απαντηθεί." -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1110 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1671 msgid "This answer was marked correct because the primary answer is correct." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:85 msgid "This answer will be graded at a later time." msgstr "Η απάντηση θα βαθμολογηθεί αργότερα. " +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1307 +msgid "" +"This graph does not have two vertices of odd degree and all other vertices " +"of even degree." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1376 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1383 +msgid "This graph has a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1281 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1294 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1377 +msgid "This graph is not connected." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "Αυτή είναι μια νέα (επανατυχαιοποιημένη) έκδοση του προβλήματος." -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1331 +msgid "This path does not traverse all edges." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1343 +msgid "This path is not a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3099 msgid "This problem contains a video which must be viewed online." msgstr "" "Αυτό το πρόβλημα περιέχει ένα βίντεο που πρέπει να προβληθεί στο διαδίκτυο." @@ -208,19 +336,22 @@ msgstr "Αυτό το πρόβλημα έχει περισσότερα από έ msgid "True" msgstr "Σωστό" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:62 msgid "Ungraded" msgstr "Χωρίς βαθμό" -#: /opt/webwork/pg/macros/PG.pl:1280 +#: /opt/webwork/pg/lib/DragNDrop.pm:225 +msgid "Universal Set" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1128 +msgid "Unknown" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1295 msgid "You Entered" msgstr "Εισάγατε" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 -msgid "You can earn partial credit on this problem." -msgstr "Μπορείτε να κερδίσετε μερική πίστωση σε αυτό το πρόβλημα." - #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." msgstr "" @@ -230,7 +361,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "Μη δεκτές αλλαγές σε απαντήσεις αν συνεχίσετε στο επόμενο μέρος!" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3092 msgid "Your browser does not support the video tag." msgstr "Ο περιηγητής σας δεν υποστηρίζει την ετικέτα βίντεο." @@ -238,71 +369,71 @@ msgstr "Ο περιηγητής σας δεν υποστηρίζει την ετ msgid "Your score for this attempt is for this part only;" msgstr "Το σκορ σας σε αυτήν την προσπάθεια είναι μόνο για αυτό το μέρος˙" -#. ($name) #. ($1) +#. ($name) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:650 msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2977 msgid "end image description" msgstr "" +# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -# does not need to be translated #: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 #: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "i " -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:818 msgid "if" msgstr "εάν" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2893 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2975 msgid "image description" msgstr "" +#. ($i + 1) #. (1) #. ($count) -#. ($i + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:558 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:567 msgid "option %1 " msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:821 msgid "otherwise" msgstr "αλλιώς" -#. ($2 + 1) #. ($radioIndex) +#. ($2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:652 msgid "part %1 " msgstr "" #. ($1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:645 msgid "problem %1 " msgstr "" #. ($1 + 1, $2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:658 msgid "row %1 column %2 " msgstr "" #. ($partIndex) -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:653 msgid "subpart %1 " msgstr "" diff --git a/lib/WeBWorK/PG/Localize/es.po b/lib/WeBWorK/PG/Localize/es.po index bda17f08fb..8bea7e5bac 100644 --- a/lib/WeBWorK/PG/Localize/es.po +++ b/lib/WeBWorK/PG/Localize/es.po @@ -2,10 +2,10 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# # Translators: # Glenn Rice, 2023 -# +# msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" @@ -13,72 +13,141 @@ msgstr "" "PO-Revision-Date: 2023-11-19 04:33+0000\n" "Last-Translator: Glenn Rice, 2023\n" "Language-Team: Spanish (https://app.transifex.com/webwork/teams/16644/es/)\n" +"Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: es\n" -"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" +"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? " +"1 : 2;\n" + +#. ($edgeText[0], $edgeText[1]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:813 +msgid " There are edges between %1 and %2." +msgstr "" + +#. (join($comma, @edgeText[ 0 .. $#edgeText - 1 ]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:817 +msgid " There are edges between %1%2and %3." +msgstr "" + +#. ($edgeText[0]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:811 +msgid " There is an edge between %1." +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:806 +msgid "%1 and %2" +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:803 +msgid "%1 and %2 with weight %3" +msgstr "" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1455 +#: /opt/webwork/pg/macros/PG.pl:1470 msgid "%1 of the answers %plural(%1,has,have) been graded." msgstr "" #. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1427 +#: /opt/webwork/pg/macros/PG.pl:1442 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1451 +#: /opt/webwork/pg/macros/PG.pl:1466 msgid "%1 of the answers will be graded later." msgstr "" #. ($options{resultTitle}) -#: /opt/webwork/pg/macros/PG.pl:1224 +#: /opt/webwork/pg/macros/PG.pl:1239 msgid "%1 with message" msgstr "" #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1117 +#: /opt/webwork/pg/macros/PG.pl:1119 msgid "%1% correct" msgstr "" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1440 -msgid "" -"%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "" "%quant(%1,de las preguntas sigue,de las preguntas siguen) sin respuesta." -#: /opt/webwork/pg/macros/PG.pl:1414 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:596 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:793 +msgid ", " +msgstr "" + +#. ($self->labelsString) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:791 +msgid "A graph with vertices %1." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:221 +msgid "Add Bucket" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1429 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1405 +#: /opt/webwork/pg/macros/PG.pl:1420 msgid "All of the computer gradable answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1060 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1323 +msgid "An edge traversed by this path does not exist in the graph." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1062 msgid "Answer Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +#: /opt/webwork/pg/macros/PG.pl:1255 /opt/webwork/pg/macros/PG.pl:1331 msgid "Close" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +#: /opt/webwork/pg/lib/DragNDrop.pm:233 +msgid "Close Help" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2908 msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1109 +#: /opt/webwork/pg/macros/PG.pl:1111 msgid "Correct" msgstr "Correcto" -#: /opt/webwork/pg/macros/PG.pl:1322 +#: /opt/webwork/pg/macros/PG.pl:1337 msgid "Correct Answer" msgstr "" +#: /opt/webwork/pg/lib/DragNDrop.pm:232 +msgid "Drag and Drop Help" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:241 +msgid "" +"Drag items in the universal set to copy them to a list. Tab and shift-tab " +"can be used to focus universal set items. A focused item in the universal " +"set can be added to the first list with the right or down arrow keys, or " +"added to the last list with the left or up arrow keys. A focused item in a " +"list can be removed by using the left or right arrow key until it returns to " +"the universal set." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:235 +msgid "" +"Drag to reorganize items within lists or to move items to a different list. " +"Tab and shift-tab can be used to focus list items. The left and right arrow " +"keys move a focused list item to the list to the left or right. The up and " +"down arrow keys move a focused list item up and down inside a list." +msgstr "" + #: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" @@ -98,7 +167,7 @@ msgstr "" msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:64 msgid "Graded" msgstr "" @@ -106,11 +175,11 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1305 msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1304 msgid "Hint:" msgstr "Pista:" @@ -118,20 +187,40 @@ msgstr "Pista:" msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1113 +#: /opt/webwork/pg/macros/PG.pl:1115 /opt/webwork/pg/macros/PG.pl:1132 msgid "Incorrect" msgstr "incorrecto" +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:227 +msgid "Item %1 in the universal set added as item %2 to list %3." +msgstr "" + +#. ('%1s', '%2s') +#: /opt/webwork/pg/lib/DragNDrop.pm:228 +msgid "Item %1 removed from list %2." +msgstr "" + +#. ('%1s', '%2s', '%3s', '%4s') +#: /opt/webwork/pg/lib/DragNDrop.pm:231 +msgid "Moved item %1 in list %2 to item %3 in list %4." +msgstr "" + +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:229 +msgid "Moved item %1 in list %2 to item %3." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "Nota:" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:139 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1285 +#: /opt/webwork/pg/macros/PG.pl:1300 msgid "Preview of Your Answer" msgstr "" @@ -140,7 +229,19 @@ msgstr "" msgid "Quick Entry" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1328 +#: /opt/webwork/pg/lib/DragNDrop.pm:222 +msgid "Remove" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:220 +msgid "Reset" +msgstr "" + +#: /opt/webwork/pg/macros/graph/AppletObjects.pl:115 +msgid "Return this question to its initial state" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1343 msgid "Reveal" msgstr "" @@ -148,11 +249,11 @@ msgstr "" msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1297 msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1296 msgid "Solution:" msgstr "Solución:" @@ -160,40 +261,69 @@ msgstr "Solución:" msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1375 +#: /opt/webwork/pg/macros/PG.pl:1390 msgid "The answer has been graded." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1393 +#: /opt/webwork/pg/macros/PG.pl:1408 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1364 +#: /opt/webwork/pg/macros/PG.pl:1379 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1374 +#: /opt/webwork/pg/macros/PG.pl:1389 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1384 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1284 +msgid "The degrees of the vertices in this graph are not all even." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1399 msgid "The question has not been answered." msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1110 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1671 msgid "This answer was marked correct because the primary answer is correct." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:85 msgid "This answer will be graded at a later time." msgstr "" +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1307 +msgid "" +"This graph does not have two vertices of odd degree and all other vertices " +"of even degree." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1376 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1383 +msgid "This graph has a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1281 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1294 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1377 +msgid "This graph is not connected." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1331 +msgid "This path does not traverse all edges." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1343 +msgid "This path is not a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3099 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -205,18 +335,21 @@ msgstr "" msgid "True" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:62 msgid "Ungraded" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1280 -msgid "You Entered" +#: /opt/webwork/pg/lib/DragNDrop.pm:225 +msgid "Universal Set" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1128 +msgid "Unknown" msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 -msgid "You can earn partial credit on this problem." -msgstr "Puedes obtener una fracción del puntaje total en este problema." +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "You Entered" +msgstr "" #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." @@ -226,7 +359,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3092 msgid "Your browser does not support the video tag." msgstr "" @@ -234,71 +367,71 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#. ($name) #. ($1) +#. ($name) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:650 msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2977 msgid "end image description" msgstr "" +# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -# does not need to be translated #: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 #: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:818 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2893 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2975 msgid "image description" msgstr "" +#. ($i + 1) #. (1) #. ($count) -#. ($i + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:558 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:567 msgid "option %1 " msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:821 msgid "otherwise" msgstr "" -#. ($2 + 1) #. ($radioIndex) +#. ($2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:652 msgid "part %1 " msgstr "" #. ($1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:645 msgid "problem %1 " msgstr "" #. ($1 + 1, $2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:658 msgid "row %1 column %2 " msgstr "" #. ($partIndex) -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:653 msgid "subpart %1 " msgstr "" diff --git a/lib/WeBWorK/PG/Localize/fr-CA.po b/lib/WeBWorK/PG/Localize/fr-CA.po index 81c54deb2b..6465d37f28 100644 --- a/lib/WeBWorK/PG/Localize/fr-CA.po +++ b/lib/WeBWorK/PG/Localize/fr-CA.po @@ -1,20 +1,19 @@ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. # # Translators: -# Hélène Larue , 2017 -# Jonathan Desaulniers , 2018-2019,2021-2022 -# Julie Tremblay , 2016-2017 +# Glenn Rice, 2023 +# msgid "" msgstr "" -"Project-Id-Version: webwork2\n" +"Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-10-05 06:43-0500\n" -"Last-Translator: Jonathan Desaulniers , 2018-2019,2021-2022\n" -"Language-Team: French (Canada) (http://app.transifex.com/webwork/webwork2/" -"language/fr_CA/)\n" +"PO-Revision-Date: 2023-11-19 04:33+0000\n" +"Last-Translator: Glenn Rice, 2023\n" +"Language-Team: French (Canada) (https://app.transifex.com/webwork/teams/" +"16644/fr_CA/)\n" "Language: fr_CA\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -22,240 +21,431 @@ msgstr "" "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % " "1000000 == 0 ? 1 : 2;\n" -#. (@answerNames - $numBlank - $numCorrect - $numEssay) -#: /opt/webwork/pg/macros/PG.pl:1412 +#. ($edgeText[0], $edgeText[1]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:813 +msgid " There are edges between %1 and %2." +msgstr "" + +#. (join($comma, @edgeText[ 0 .. $#edgeText - 1 ]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:817 +msgid " There are edges between %1%2and %3." +msgstr "" + +#. ($edgeText[0]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:811 +msgid " There is an edge between %1." +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:806 +msgid "%1 and %2" +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:803 +msgid "%1 and %2 with weight %3" +msgstr "" + +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1470 +msgid "%1 of the answers %plural(%1,has,have) been graded." +msgstr "" + +#. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1442 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" -#. ($numEssay) -#: /opt/webwork/pg/macros/PG.pl:1435 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1466 msgid "%1 of the answers will be graded later." msgstr "" +#. ($options{resultTitle}) +#: /opt/webwork/pg/macros/PG.pl:1239 +msgid "%1 with message" +msgstr "" + #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1157 +#: /opt/webwork/pg/macros/PG.pl:1119 msgid "%1% correct" msgstr "%1% correct" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1425 +#: /opt/webwork/pg/macros/PG.pl:1455 msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "%quant(%1, question reste, questions restent) sans réponses." -#: /opt/webwork/pg/macros/PG.pl:1399 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:596 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:793 +msgid ", " +msgstr "" + +#. ($self->labelsString) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:791 +msgid "A graph with vertices %1." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:221 +msgid "Add Bucket" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1429 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1390 -msgid "All of the gradeable answers are correct." +#: /opt/webwork/pg/macros/PG.pl:1420 +msgid "All of the computer gradable answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1149 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1323 +msgid "An edge traversed by this path does not exist in the graph." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1062 +msgid "Answer Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1255 /opt/webwork/pg/macros/PG.pl:1331 +msgid "Close" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:233 +msgid "Close Help" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2908 +msgid "Close image description" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1111 msgid "Correct" msgstr "Réussi" -#: /opt/webwork/pg/macros/PG.pl:1308 +#: /opt/webwork/pg/macros/PG.pl:1337 msgid "Correct Answer" msgstr "Bonne réponse" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 +#: /opt/webwork/pg/lib/DragNDrop.pm:232 +msgid "Drag and Drop Help" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:241 +msgid "" +"Drag items in the universal set to copy them to a list. Tab and shift-tab " +"can be used to focus universal set items. A focused item in the universal " +"set can be added to the first list with the right or down arrow keys, or " +"added to the last list with the left or up arrow keys. A focused item in a " +"list can be removed by using the left or right arrow key until it returns to " +"the universal set." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:235 +msgid "" +"Drag to reorganize items within lists or to move items to a different list. " +"Tab and shift-tab can be used to focus list items. The left and right arrow " +"keys move a focused list item to the list to the left or right. The up and " +"down arrow keys move a focused list item up and down inside a list." +msgstr "" + +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "Faux" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:186 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:187 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:172 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:173 msgid "Get a new version of this problem" msgstr "Obtenir une nouvelle version du problème" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:483 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:483 msgid "Go back to Part 1" msgstr "Retour à la partie 1" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:293 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:498 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:512 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:293 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:498 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:512 msgid "Go on to next part" msgstr "Aller à la partie suivante" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:427 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:64 +msgid "Graded" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:413 msgid "Hardcopy will always print the original version of the problem." msgstr "" "Le document sera toujours imprimer avec la version originale du problème." -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1305 +msgid "Hint" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1304 msgid "Hint:" msgstr "Indice :" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 -msgid "Hint: " -msgstr "Indice:" - -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:425 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:411 msgid "If you come back to it later, it may revert to its original version." msgstr "" "Si vous revenez plus tard, le problème pourrait être revenu à sa version " "originale." -#: /opt/webwork/pg/macros/PG.pl:1153 +#: /opt/webwork/pg/macros/PG.pl:1115 /opt/webwork/pg/macros/PG.pl:1132 msgid "Incorrect" msgstr "Erroné" -#: /opt/webwork/pg/macros/PG.pl:1319 -msgid "Message" -msgstr "Message" +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:227 +msgid "Item %1 in the universal set added as item %2 to list %3." +msgstr "" + +#. ('%1s', '%2s') +#: /opt/webwork/pg/lib/DragNDrop.pm:228 +msgid "Item %1 removed from list %2." +msgstr "" + +#. ('%1s', '%2s', '%3s', '%4s') +#: /opt/webwork/pg/lib/DragNDrop.pm:231 +msgid "Moved item %1 in list %2 to item %3 in list %4." +msgstr "" + +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:229 +msgid "Moved item %1 in list %2 to item %3." +msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "Note:" -#: /opt/webwork/pg/macros/PG.pl:1108 -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:139 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1295 +#: /opt/webwork/pg/macros/PG.pl:1300 msgid "Preview of Your Answer" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:44 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:85 +msgid "Quick Entry" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:222 +msgid "Remove" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:220 +msgid "Reset" +msgstr "" + +#: /opt/webwork/pg/macros/graph/AppletObjects.pl:115 +msgid "Return this question to its initial state" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1343 +msgid "Reveal" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:174 msgid "Set random seed to:" msgstr "Définir la source aléatoire à:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1297 +msgid "Solution" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1296 msgid "Solution:" msgstr "Solution :" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 -msgid "Solution: " -msgstr "Solution:" - -#: /opt/webwork/pg/macros/core/compoundProblem.pl:525 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:525 msgid "Submit your answers again to go on to the next part." msgstr "Soumettre votre réponse à nouveau pour passer à la partie suivante." -#: /opt/webwork/pg/macros/PG.pl:1378 +#: /opt/webwork/pg/macros/PG.pl:1390 +msgid "The answer has been graded." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1408 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1351 +#: /opt/webwork/pg/macros/PG.pl:1379 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1360 +#: /opt/webwork/pg/macros/PG.pl:1389 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1369 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1284 +msgid "The degrees of the vertices in this graph are not all even." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1399 msgid "The question has not been answered." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1110 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1671 +msgid "This answer was marked correct because the primary answer is correct." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:85 msgid "This answer will be graded at a later time." msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1307 +msgid "" +"This graph does not have two vertices of odd degree and all other vertices " +"of even degree." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1376 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1383 +msgid "This graph has a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1281 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1294 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1377 +msgid "This graph is not connected." +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "Voici une nouvelle version (à nouveau randomisée) du problème." -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 -msgid "" -"This is an essay answer text box. You can type your answer in here and, " -"after you hit submit, it will be saved so that your instructor can grade it " -"at a later date. If your instructor makes any comments on your answer those " -"comments will appear on this page after the question has been graded. You " -"can use LaTeX to make your math equations look pretty. LaTeX expressions " -"should be enclosed using the parenthesis notation and not dollar signs." +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1331 +msgid "This path does not traverse all edges." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1343 +msgid "This path is not a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3099 msgid "This problem contains a video which must be viewed online." msgstr "Ce problème contient une vidéo qui doit être consultée en ligne." -#: /opt/webwork/pg/macros/core/compoundProblem.pl:631 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:631 msgid "This problem has more than one part." msgstr "Ce problème comporte plusieurs parties." -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:560 msgid "True" msgstr "Vrai" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:62 msgid "Ungraded" msgstr "Non classé" -#: /opt/webwork/pg/macros/PG.pl:1290 -msgid "You Entered" +#: /opt/webwork/pg/lib/DragNDrop.pm:225 +msgid "Universal Set" msgstr "" -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 -msgid "You can earn partial credit on this problem." -msgstr "Vous pouvez obtenir une partie des points pour ce problème." +#: /opt/webwork/pg/macros/PG.pl:1128 +msgid "Unknown" +msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:397 +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "You Entered" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." msgstr "" "Vous pouvez obtenir une nouvelle version du problème après la date de remise." -#: /opt/webwork/pg/macros/core/compoundProblem.pl:646 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:646 msgid "You may not change your answers when going on to the next part!" msgstr "" "Vous ne pouvez pas modifier vos réponses en passant à la partie suivante!" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3092 msgid "Your browser does not support the video tag." msgstr "Votre navigateur ne supporte pas l'environnement vidéo." -#: /opt/webwork/pg/macros/core/compoundProblem.pl:634 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:634 msgid "Your score for this attempt is for this part only;" msgstr "" "Votre résultat pour cette tentative correspond à cette partie seulement;" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 -msgid "answer" -msgstr "réponse" +#. ($1) +#. ($name) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:650 +msgid "answer %1 " +msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "column" -msgstr "colonne" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2977 +msgid "end image description" +msgstr "" # does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -#: /opt/webwork/pg/lib/Parser/List/Vector.pm:39 +#: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 -#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:95 +#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "Je" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:840 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:818 msgid "if" msgstr "Si" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:843 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2893 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2975 +msgid "image description" +msgstr "" + +#. ($i + 1) +#. (1) +#. ($count) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:558 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:567 +msgid "option %1 " +msgstr "" + +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:821 msgid "otherwise" msgstr "autrement" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 -msgid "part" -msgstr "partie" +#. ($radioIndex) +#. ($2 + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:652 +msgid "part %1 " +msgstr "" + +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:645 +msgid "problem %1 " +msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 -msgid "problem" -msgstr "problème" +#. ($1 + 1, $2 + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:658 +msgid "row %1 column %2 " +msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "row" -msgstr "ligne" +#. ($partIndex) +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:653 +msgid "subpart %1 " +msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:485 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:500 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:485 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:500 msgid "when you submit your answers" msgstr "Quand vous soumettez vos réponses" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:638 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:638 msgid "your overall score is for all the parts combined." msgstr "Votre résultat d'ensemble est pour chacune des parties combinées." diff --git a/lib/WeBWorK/PG/Localize/fr.po b/lib/WeBWorK/PG/Localize/fr.po index a5413aada1..d274183e99 100644 --- a/lib/WeBWorK/PG/Localize/fr.po +++ b/lib/WeBWorK/PG/Localize/fr.po @@ -2,10 +2,10 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# # Translators: # Glenn Rice, 2023 -# +# msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" @@ -13,71 +13,140 @@ msgstr "" "PO-Revision-Date: 2023-11-19 04:33+0000\n" "Last-Translator: Glenn Rice, 2023\n" "Language-Team: French (https://app.transifex.com/webwork/teams/16644/fr/)\n" +"Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: fr\n" -"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" +"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % " +"1000000 == 0 ? 1 : 2;\n" + +#. ($edgeText[0], $edgeText[1]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:813 +msgid " There are edges between %1 and %2." +msgstr "" + +#. (join($comma, @edgeText[ 0 .. $#edgeText - 1 ]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:817 +msgid " There are edges between %1%2and %3." +msgstr "" + +#. ($edgeText[0]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:811 +msgid " There is an edge between %1." +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:806 +msgid "%1 and %2" +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:803 +msgid "%1 and %2 with weight %3" +msgstr "" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1455 +#: /opt/webwork/pg/macros/PG.pl:1470 msgid "%1 of the answers %plural(%1,has,have) been graded." msgstr "" #. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1427 +#: /opt/webwork/pg/macros/PG.pl:1442 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1451 +#: /opt/webwork/pg/macros/PG.pl:1466 msgid "%1 of the answers will be graded later." msgstr "" #. ($options{resultTitle}) -#: /opt/webwork/pg/macros/PG.pl:1224 +#: /opt/webwork/pg/macros/PG.pl:1239 msgid "%1 with message" msgstr "" #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1117 +#: /opt/webwork/pg/macros/PG.pl:1119 msgid "%1% correct" msgstr "%1% correct" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1440 -msgid "" -"%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "%quant(%1, des questions restent, des questions reste) sans réponse." -#: /opt/webwork/pg/macros/PG.pl:1414 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:596 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:793 +msgid ", " +msgstr "" + +#. ($self->labelsString) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:791 +msgid "A graph with vertices %1." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:221 +msgid "Add Bucket" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1429 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1405 +#: /opt/webwork/pg/macros/PG.pl:1420 msgid "All of the computer gradable answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1060 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1323 +msgid "An edge traversed by this path does not exist in the graph." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1062 msgid "Answer Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +#: /opt/webwork/pg/macros/PG.pl:1255 /opt/webwork/pg/macros/PG.pl:1331 msgid "Close" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +#: /opt/webwork/pg/lib/DragNDrop.pm:233 +msgid "Close Help" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2908 msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1109 +#: /opt/webwork/pg/macros/PG.pl:1111 msgid "Correct" msgstr "Correct" -#: /opt/webwork/pg/macros/PG.pl:1322 +#: /opt/webwork/pg/macros/PG.pl:1337 msgid "Correct Answer" msgstr "" +#: /opt/webwork/pg/lib/DragNDrop.pm:232 +msgid "Drag and Drop Help" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:241 +msgid "" +"Drag items in the universal set to copy them to a list. Tab and shift-tab " +"can be used to focus universal set items. A focused item in the universal " +"set can be added to the first list with the right or down arrow keys, or " +"added to the last list with the left or up arrow keys. A focused item in a " +"list can be removed by using the left or right arrow key until it returns to " +"the universal set." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:235 +msgid "" +"Drag to reorganize items within lists or to move items to a different list. " +"Tab and shift-tab can be used to focus list items. The left and right arrow " +"keys move a focused list item to the list to the left or right. The up and " +"down arrow keys move a focused list item up and down inside a list." +msgstr "" + #: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" @@ -97,7 +166,7 @@ msgstr "" msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:64 msgid "Graded" msgstr "" @@ -105,11 +174,11 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1305 msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1304 msgid "Hint:" msgstr "" @@ -117,20 +186,40 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1113 +#: /opt/webwork/pg/macros/PG.pl:1115 /opt/webwork/pg/macros/PG.pl:1132 msgid "Incorrect" msgstr "" +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:227 +msgid "Item %1 in the universal set added as item %2 to list %3." +msgstr "" + +#. ('%1s', '%2s') +#: /opt/webwork/pg/lib/DragNDrop.pm:228 +msgid "Item %1 removed from list %2." +msgstr "" + +#. ('%1s', '%2s', '%3s', '%4s') +#: /opt/webwork/pg/lib/DragNDrop.pm:231 +msgid "Moved item %1 in list %2 to item %3 in list %4." +msgstr "" + +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:229 +msgid "Moved item %1 in list %2 to item %3." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:139 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1285 +#: /opt/webwork/pg/macros/PG.pl:1300 msgid "Preview of Your Answer" msgstr "" @@ -139,7 +228,19 @@ msgstr "" msgid "Quick Entry" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1328 +#: /opt/webwork/pg/lib/DragNDrop.pm:222 +msgid "Remove" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:220 +msgid "Reset" +msgstr "" + +#: /opt/webwork/pg/macros/graph/AppletObjects.pl:115 +msgid "Return this question to its initial state" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1343 msgid "Reveal" msgstr "" @@ -147,11 +248,11 @@ msgstr "" msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1297 msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1296 msgid "Solution:" msgstr "" @@ -159,40 +260,69 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1375 +#: /opt/webwork/pg/macros/PG.pl:1390 msgid "The answer has been graded." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1393 +#: /opt/webwork/pg/macros/PG.pl:1408 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1364 +#: /opt/webwork/pg/macros/PG.pl:1379 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1374 +#: /opt/webwork/pg/macros/PG.pl:1389 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1384 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1284 +msgid "The degrees of the vertices in this graph are not all even." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1399 msgid "The question has not been answered." msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1110 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1671 msgid "This answer was marked correct because the primary answer is correct." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:85 msgid "This answer will be graded at a later time." msgstr "" +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1307 +msgid "" +"This graph does not have two vertices of odd degree and all other vertices " +"of even degree." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1376 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1383 +msgid "This graph has a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1281 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1294 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1377 +msgid "This graph is not connected." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1331 +msgid "This path does not traverse all edges." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1343 +msgid "This path is not a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3099 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -204,18 +334,21 @@ msgstr "" msgid "True" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:62 msgid "Ungraded" msgstr "Mise à jour réussie" -#: /opt/webwork/pg/macros/PG.pl:1280 -msgid "You Entered" +#: /opt/webwork/pg/lib/DragNDrop.pm:225 +msgid "Universal Set" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1128 +msgid "Unknown" msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 -msgid "You can earn partial credit on this problem." -msgstr "Vous pouvez obtenir une partie des points pour ce problème." +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "You Entered" +msgstr "" #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." @@ -225,7 +358,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3092 msgid "Your browser does not support the video tag." msgstr "" @@ -233,71 +366,71 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#. ($name) #. ($1) +#. ($name) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:650 msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2977 msgid "end image description" msgstr "" +# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -# does not need to be translated #: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 #: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:818 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2893 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2975 msgid "image description" msgstr "" +#. ($i + 1) #. (1) #. ($count) -#. ($i + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:558 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:567 msgid "option %1 " msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:821 msgid "otherwise" msgstr "" -#. ($2 + 1) #. ($radioIndex) +#. ($2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:652 msgid "part %1 " msgstr "" #. ($1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:645 msgid "problem %1 " msgstr "" #. ($1 + 1, $2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:658 msgid "row %1 column %2 " msgstr "" #. ($partIndex) -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:653 msgid "subpart %1 " msgstr "" diff --git a/lib/WeBWorK/PG/Localize/he-IL.po b/lib/WeBWorK/PG/Localize/he-IL.po index bd6f9220fb..989784402d 100644 --- a/lib/WeBWorK/PG/Localize/he-IL.po +++ b/lib/WeBWorK/PG/Localize/he-IL.po @@ -2,83 +2,153 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# # Translators: # Glenn Rice, 2023 # Nathan Wallach , 2024 -# +# msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: 2023-11-19 04:33+0000\n" "Last-Translator: Nathan Wallach , 2024\n" -"Language-Team: Hebrew (Israel) (https://app.transifex.com/webwork/teams/16644/he_IL/)\n" +"Language-Team: Hebrew (Israel) (https://app.transifex.com/webwork/teams/" +"16644/he_IL/)\n" +"Language: he_IL\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: he_IL\n" -"Plural-Forms: nplurals=3; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: 2;\n" +"Plural-Forms: nplurals=3; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % " +"1 == 0) ? 1: 2;\n" + +#. ($edgeText[0], $edgeText[1]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:813 +msgid " There are edges between %1 and %2." +msgstr "" + +#. (join($comma, @edgeText[ 0 .. $#edgeText - 1 ]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:817 +msgid " There are edges between %1%2and %3." +msgstr "" + +#. ($edgeText[0]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:811 +msgid " There is an edge between %1." +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:806 +msgid "%1 and %2" +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:803 +msgid "%1 and %2 with weight %3" +msgstr "" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1455 +#: /opt/webwork/pg/macros/PG.pl:1470 msgid "%1 of the answers %plural(%1,has,have) been graded." msgstr "%1 מהתשובות %plural(%1,נבדק,נבדקו)." #. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1427 +#: /opt/webwork/pg/macros/PG.pl:1442 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "%1 מהתשובות %plural(%1,אינה נכונה,אינן נכונות)." #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1451 +#: /opt/webwork/pg/macros/PG.pl:1466 msgid "%1 of the answers will be graded later." msgstr "%1 מהתשובות ייבדקו בעתיד." #. ($options{resultTitle}) -#: /opt/webwork/pg/macros/PG.pl:1224 +#: /opt/webwork/pg/macros/PG.pl:1239 msgid "%1 with message" msgstr "%1 עם הודעה" #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1117 +#: /opt/webwork/pg/macros/PG.pl:1119 msgid "%1% correct" msgstr "%1% נכון" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1440 -msgid "" -"%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "%quant(%1, מהשאלות לא נענתה, מהשאלות לא נענו)." -#: /opt/webwork/pg/macros/PG.pl:1414 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:596 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:793 +msgid ", " +msgstr "" + +#. ($self->labelsString) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:791 +msgid "A graph with vertices %1." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:221 +msgid "Add Bucket" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1429 msgid "All of the answers are correct." msgstr "כל התשובות הן נכונות." -#: /opt/webwork/pg/macros/PG.pl:1405 +#: /opt/webwork/pg/macros/PG.pl:1420 msgid "All of the computer gradable answers are correct." msgstr "כל התשובות הניתנות לבדיקה אוטומטית הן נכונות." -#: /opt/webwork/pg/macros/PG.pl:1060 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1323 +msgid "An edge traversed by this path does not exist in the graph." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1062 msgid "Answer Preview" msgstr "תצוגה מקדימה של התשובות" -#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +#: /opt/webwork/pg/macros/PG.pl:1255 /opt/webwork/pg/macros/PG.pl:1331 msgid "Close" msgstr "סגור" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +#: /opt/webwork/pg/lib/DragNDrop.pm:233 +msgid "Close Help" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2908 msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1109 +#: /opt/webwork/pg/macros/PG.pl:1111 msgid "Correct" msgstr "נכון" -#: /opt/webwork/pg/macros/PG.pl:1322 +#: /opt/webwork/pg/macros/PG.pl:1337 msgid "Correct Answer" msgstr "תשובה נכונה" +#: /opt/webwork/pg/lib/DragNDrop.pm:232 +msgid "Drag and Drop Help" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:241 +msgid "" +"Drag items in the universal set to copy them to a list. Tab and shift-tab " +"can be used to focus universal set items. A focused item in the universal " +"set can be added to the first list with the right or down arrow keys, or " +"added to the last list with the left or up arrow keys. A focused item in a " +"list can be removed by using the left or right arrow key until it returns to " +"the universal set." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:235 +msgid "" +"Drag to reorganize items within lists or to move items to a different list. " +"Tab and shift-tab can be used to focus list items. The left and right arrow " +"keys move a focused list item to the list to the left or right. The up and " +"down arrow keys move a focused list item up and down inside a list." +msgstr "" + #: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr " לא נכון" @@ -98,7 +168,7 @@ msgstr "חזור לחלק 1" msgid "Go on to next part" msgstr "המשך לחלק הבא" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:64 msgid "Graded" msgstr "נבדק" @@ -106,11 +176,11 @@ msgstr "נבדק" msgid "Hardcopy will always print the original version of the problem." msgstr "בקבצי הדפסה תמיד יודפס הגרסה המקורית של השאלה." -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1305 msgid "Hint" msgstr "רמז" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1304 msgid "Hint:" msgstr "רמז:" @@ -118,20 +188,40 @@ msgstr "רמז:" msgid "If you come back to it later, it may revert to its original version." msgstr "אם תחזור לשאלה בהמשך, ייתכן שהשאלה תחזור לגרסה המקורית." -#: /opt/webwork/pg/macros/PG.pl:1113 +#: /opt/webwork/pg/macros/PG.pl:1115 /opt/webwork/pg/macros/PG.pl:1132 msgid "Incorrect" msgstr "לא נכון" +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:227 +msgid "Item %1 in the universal set added as item %2 to list %3." +msgstr "" + +#. ('%1s', '%2s') +#: /opt/webwork/pg/lib/DragNDrop.pm:228 +msgid "Item %1 removed from list %2." +msgstr "" + +#. ('%1s', '%2s', '%3s', '%4s') +#: /opt/webwork/pg/lib/DragNDrop.pm:231 +msgid "Moved item %1 in list %2 to item %3 in list %4." +msgstr "" + +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:229 +msgid "Moved item %1 in list %2 to item %3." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "הערה:" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:139 msgid "Preview" msgstr "תצוגה מקדימה" -#: /opt/webwork/pg/macros/PG.pl:1285 +#: /opt/webwork/pg/macros/PG.pl:1300 msgid "Preview of Your Answer" msgstr "תצוגה מקדימה של התשובה שלך" @@ -140,7 +230,19 @@ msgstr "תצוגה מקדימה של התשובה שלך" msgid "Quick Entry" msgstr "הזנה מהירה" -#: /opt/webwork/pg/macros/PG.pl:1328 +#: /opt/webwork/pg/lib/DragNDrop.pm:222 +msgid "Remove" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:220 +msgid "Reset" +msgstr "" + +#: /opt/webwork/pg/macros/graph/AppletObjects.pl:115 +msgid "Return this question to its initial state" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1343 msgid "Reveal" msgstr "הצג" @@ -148,11 +250,11 @@ msgstr "הצג" msgid "Set random seed to:" msgstr "החלף את זרע ההגרלות אל:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1297 msgid "Solution" msgstr "פתרון" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1296 msgid "Solution:" msgstr "פתרון:" @@ -160,40 +262,69 @@ msgstr "פתרון:" msgid "Submit your answers again to go on to the next part." msgstr "שלח את התשובות שוב כדי להתקדם לחלק הבא." -#: /opt/webwork/pg/macros/PG.pl:1375 +#: /opt/webwork/pg/macros/PG.pl:1390 msgid "The answer has been graded." msgstr "התשובה נבדקה" -#: /opt/webwork/pg/macros/PG.pl:1393 +#: /opt/webwork/pg/macros/PG.pl:1408 msgid "The answer is NOT correct." msgstr "התשובה איננה נכונה" -#: /opt/webwork/pg/macros/PG.pl:1364 +#: /opt/webwork/pg/macros/PG.pl:1379 msgid "The answer is correct." msgstr "התשובה נכונה" -#: /opt/webwork/pg/macros/PG.pl:1374 +#: /opt/webwork/pg/macros/PG.pl:1389 msgid "The answer will be graded later." msgstr "התשובה תבדק בהמשך" -#: /opt/webwork/pg/macros/PG.pl:1384 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1284 +msgid "The degrees of the vertices in this graph are not all even." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1399 msgid "The question has not been answered." msgstr " השאלה לא נענתה" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1110 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1671 msgid "This answer was marked correct because the primary answer is correct." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:85 msgid "This answer will be graded at a later time." msgstr "התשובה הזאת תבדק בעתיד" +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1307 +msgid "" +"This graph does not have two vertices of odd degree and all other vertices " +"of even degree." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1376 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1383 +msgid "This graph has a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1281 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1294 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1377 +msgid "This graph is not connected." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "הנה גרסה חדשה של השאלה. (הוגרלו מספרים חדשים)" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1331 +msgid "This path does not traverse all edges." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1343 +msgid "This path is not a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3099 msgid "This problem contains a video which must be viewed online." msgstr "השאלה מכילה וידיאו שיש לראות באופן מקוון." @@ -205,19 +336,22 @@ msgstr "לשאלה זו יש יותר מחלק אחד." msgid "True" msgstr "נכון" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:62 msgid "Ungraded" msgstr "טרם נבדק" -#: /opt/webwork/pg/macros/PG.pl:1280 +#: /opt/webwork/pg/lib/DragNDrop.pm:225 +msgid "Universal Set" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1128 +msgid "Unknown" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1295 msgid "You Entered" msgstr "הזנת" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 -msgid "You can earn partial credit on this problem." -msgstr "ניתן לקבל ניקוד חלקי בשאלה זו" - #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." msgstr "אתה יכול לבקש גרסה שונה של שאלה זו לאחר מועד ההגשה." @@ -226,7 +360,7 @@ msgstr "אתה יכול לבקש גרסה שונה של שאלה זו לאחר msgid "You may not change your answers when going on to the next part!" msgstr "לא ניתן לשנות תשובות כאשר אתה מתקדם לחלק הבא!" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3092 msgid "Your browser does not support the video tag." msgstr "הדפדפן שלך לא תומך ב-video tag." @@ -234,71 +368,71 @@ msgstr "הדפדפן שלך לא תומך ב-video tag." msgid "Your score for this attempt is for this part only;" msgstr "הציון שקבלת בהגשה זו הוא רק לחקל זה;" -#. ($name) #. ($1) +#. ($name) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:650 msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2977 msgid "end image description" msgstr "" +# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -# does not need to be translated #: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 #: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "i" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:818 msgid "if" msgstr "כאשר" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2893 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2975 msgid "image description" msgstr "" +#. ($i + 1) #. (1) #. ($count) -#. ($i + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:558 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:567 msgid "option %1 " msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:821 msgid "otherwise" msgstr "אחרת" -#. ($2 + 1) #. ($radioIndex) +#. ($2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:652 msgid "part %1 " msgstr "" #. ($1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:645 msgid "problem %1 " msgstr "" #. ($1 + 1, $2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:658 msgid "row %1 column %2 " msgstr "" #. ($partIndex) -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:653 msgid "subpart %1 " msgstr "" diff --git a/lib/WeBWorK/PG/Localize/hu.po b/lib/WeBWorK/PG/Localize/hu.po index 62917628fe..24d428ed19 100644 --- a/lib/WeBWorK/PG/Localize/hu.po +++ b/lib/WeBWorK/PG/Localize/hu.po @@ -2,82 +2,151 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# # Translators: # Glenn Rice, 2023 -# +# msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: 2023-11-19 04:33+0000\n" "Last-Translator: Glenn Rice, 2023\n" -"Language-Team: Hungarian (https://app.transifex.com/webwork/teams/16644/hu/)\n" +"Language-Team: Hungarian (https://app.transifex.com/webwork/teams/16644/" +"hu/)\n" +"Language: hu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: hu\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +#. ($edgeText[0], $edgeText[1]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:813 +msgid " There are edges between %1 and %2." +msgstr "" + +#. (join($comma, @edgeText[ 0 .. $#edgeText - 1 ]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:817 +msgid " There are edges between %1%2and %3." +msgstr "" + +#. ($edgeText[0]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:811 +msgid " There is an edge between %1." +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:806 +msgid "%1 and %2" +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:803 +msgid "%1 and %2 with weight %3" +msgstr "" + #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1455 +#: /opt/webwork/pg/macros/PG.pl:1470 msgid "%1 of the answers %plural(%1,has,have) been graded." msgstr "" #. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1427 +#: /opt/webwork/pg/macros/PG.pl:1442 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1451 +#: /opt/webwork/pg/macros/PG.pl:1466 msgid "%1 of the answers will be graded later." msgstr "" #. ($options{resultTitle}) -#: /opt/webwork/pg/macros/PG.pl:1224 +#: /opt/webwork/pg/macros/PG.pl:1239 msgid "%1 with message" msgstr "" #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1117 +#: /opt/webwork/pg/macros/PG.pl:1119 msgid "%1% correct" msgstr "%1% helyes" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1440 -msgid "" -"%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "%quant(%1,kérdés maradt,kérdés maradt) megválaszolatlan." -#: /opt/webwork/pg/macros/PG.pl:1414 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:596 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:793 +msgid ", " +msgstr "" + +#. ($self->labelsString) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:791 +msgid "A graph with vertices %1." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:221 +msgid "Add Bucket" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1429 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1405 +#: /opt/webwork/pg/macros/PG.pl:1420 msgid "All of the computer gradable answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1060 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1323 +msgid "An edge traversed by this path does not exist in the graph." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1062 msgid "Answer Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +#: /opt/webwork/pg/macros/PG.pl:1255 /opt/webwork/pg/macros/PG.pl:1331 msgid "Close" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +#: /opt/webwork/pg/lib/DragNDrop.pm:233 +msgid "Close Help" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2908 msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1109 +#: /opt/webwork/pg/macros/PG.pl:1111 msgid "Correct" msgstr "Helyes" -#: /opt/webwork/pg/macros/PG.pl:1322 +#: /opt/webwork/pg/macros/PG.pl:1337 msgid "Correct Answer" msgstr "Helyes válasz" +#: /opt/webwork/pg/lib/DragNDrop.pm:232 +msgid "Drag and Drop Help" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:241 +msgid "" +"Drag items in the universal set to copy them to a list. Tab and shift-tab " +"can be used to focus universal set items. A focused item in the universal " +"set can be added to the first list with the right or down arrow keys, or " +"added to the last list with the left or up arrow keys. A focused item in a " +"list can be removed by using the left or right arrow key until it returns to " +"the universal set." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:235 +msgid "" +"Drag to reorganize items within lists or to move items to a different list. " +"Tab and shift-tab can be used to focus list items. The left and right arrow " +"keys move a focused list item to the list to the left or right. The up and " +"down arrow keys move a focused list item up and down inside a list." +msgstr "" + #: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" @@ -97,7 +166,7 @@ msgstr "" msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:64 msgid "Graded" msgstr "" @@ -105,11 +174,11 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1305 msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1304 msgid "Hint:" msgstr "" @@ -117,20 +186,40 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1113 +#: /opt/webwork/pg/macros/PG.pl:1115 /opt/webwork/pg/macros/PG.pl:1132 msgid "Incorrect" msgstr "" +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:227 +msgid "Item %1 in the universal set added as item %2 to list %3." +msgstr "" + +#. ('%1s', '%2s') +#: /opt/webwork/pg/lib/DragNDrop.pm:228 +msgid "Item %1 removed from list %2." +msgstr "" + +#. ('%1s', '%2s', '%3s', '%4s') +#: /opt/webwork/pg/lib/DragNDrop.pm:231 +msgid "Moved item %1 in list %2 to item %3 in list %4." +msgstr "" + +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:229 +msgid "Moved item %1 in list %2 to item %3." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:139 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1285 +#: /opt/webwork/pg/macros/PG.pl:1300 msgid "Preview of Your Answer" msgstr "" @@ -139,7 +228,19 @@ msgstr "" msgid "Quick Entry" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1328 +#: /opt/webwork/pg/lib/DragNDrop.pm:222 +msgid "Remove" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:220 +msgid "Reset" +msgstr "" + +#: /opt/webwork/pg/macros/graph/AppletObjects.pl:115 +msgid "Return this question to its initial state" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1343 msgid "Reveal" msgstr "" @@ -147,11 +248,11 @@ msgstr "" msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1297 msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1296 msgid "Solution:" msgstr "" @@ -159,40 +260,69 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1375 +#: /opt/webwork/pg/macros/PG.pl:1390 msgid "The answer has been graded." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1393 +#: /opt/webwork/pg/macros/PG.pl:1408 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1364 +#: /opt/webwork/pg/macros/PG.pl:1379 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1374 +#: /opt/webwork/pg/macros/PG.pl:1389 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1384 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1284 +msgid "The degrees of the vertices in this graph are not all even." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1399 msgid "The question has not been answered." msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1110 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1671 msgid "This answer was marked correct because the primary answer is correct." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:85 msgid "This answer will be graded at a later time." msgstr "" +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1307 +msgid "" +"This graph does not have two vertices of odd degree and all other vertices " +"of even degree." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1376 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1383 +msgid "This graph has a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1281 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1294 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1377 +msgid "This graph is not connected." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1331 +msgid "This path does not traverse all edges." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1343 +msgid "This path is not a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3099 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -204,18 +334,21 @@ msgstr "" msgid "True" msgstr "Igaz" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:62 msgid "Ungraded" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1280 -msgid "You Entered" +#: /opt/webwork/pg/lib/DragNDrop.pm:225 +msgid "Universal Set" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1128 +msgid "Unknown" msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 -msgid "You can earn partial credit on this problem." -msgstr "Ezért a feladatért részpontokat kaphat. " +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "You Entered" +msgstr "" #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." @@ -225,7 +358,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3092 msgid "Your browser does not support the video tag." msgstr "" @@ -233,71 +366,71 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#. ($name) #. ($1) +#. ($name) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:650 msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2977 msgid "end image description" msgstr "" +# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -# does not need to be translated #: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 #: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:818 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2893 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2975 msgid "image description" msgstr "" +#. ($i + 1) #. (1) #. ($count) -#. ($i + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:558 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:567 msgid "option %1 " msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:821 msgid "otherwise" msgstr "" -#. ($2 + 1) #. ($radioIndex) +#. ($2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:652 msgid "part %1 " msgstr "" #. ($1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:645 msgid "problem %1 " msgstr "" #. ($1 + 1, $2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:658 msgid "row %1 column %2 " msgstr "" #. ($partIndex) -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:653 msgid "subpart %1 " msgstr "" diff --git a/lib/WeBWorK/PG/Localize/ko.po b/lib/WeBWorK/PG/Localize/ko.po index d0e0d7b0b2..55fc3bd330 100644 --- a/lib/WeBWorK/PG/Localize/ko.po +++ b/lib/WeBWorK/PG/Localize/ko.po @@ -2,10 +2,10 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# # Translators: # Glenn Rice, 2023 -# +# msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" @@ -13,71 +13,139 @@ msgstr "" "PO-Revision-Date: 2023-11-19 04:33+0000\n" "Last-Translator: Glenn Rice, 2023\n" "Language-Team: Korean (https://app.transifex.com/webwork/teams/16644/ko/)\n" +"Language: ko\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: ko\n" "Plural-Forms: nplurals=1; plural=0;\n" +#. ($edgeText[0], $edgeText[1]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:813 +msgid " There are edges between %1 and %2." +msgstr "" + +#. (join($comma, @edgeText[ 0 .. $#edgeText - 1 ]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:817 +msgid " There are edges between %1%2and %3." +msgstr "" + +#. ($edgeText[0]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:811 +msgid " There is an edge between %1." +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:806 +msgid "%1 and %2" +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:803 +msgid "%1 and %2 with weight %3" +msgstr "" + #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1455 +#: /opt/webwork/pg/macros/PG.pl:1470 msgid "%1 of the answers %plural(%1,has,have) been graded." msgstr "" #. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1427 +#: /opt/webwork/pg/macros/PG.pl:1442 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1451 +#: /opt/webwork/pg/macros/PG.pl:1466 msgid "%1 of the answers will be graded later." msgstr "" #. ($options{resultTitle}) -#: /opt/webwork/pg/macros/PG.pl:1224 +#: /opt/webwork/pg/macros/PG.pl:1239 msgid "%1 with message" msgstr "" #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1117 +#: /opt/webwork/pg/macros/PG.pl:1119 msgid "%1% correct" msgstr "" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1440 -msgid "" -"%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:596 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:793 +msgid ", " +msgstr "" + +#. ($self->labelsString) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:791 +msgid "A graph with vertices %1." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1414 +#: /opt/webwork/pg/lib/DragNDrop.pm:221 +msgid "Add Bucket" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1429 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1405 +#: /opt/webwork/pg/macros/PG.pl:1420 msgid "All of the computer gradable answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1060 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1323 +msgid "An edge traversed by this path does not exist in the graph." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1062 msgid "Answer Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +#: /opt/webwork/pg/macros/PG.pl:1255 /opt/webwork/pg/macros/PG.pl:1331 msgid "Close" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +#: /opt/webwork/pg/lib/DragNDrop.pm:233 +msgid "Close Help" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2908 msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1109 +#: /opt/webwork/pg/macros/PG.pl:1111 msgid "Correct" msgstr "정답" -#: /opt/webwork/pg/macros/PG.pl:1322 +#: /opt/webwork/pg/macros/PG.pl:1337 msgid "Correct Answer" msgstr "정답" +#: /opt/webwork/pg/lib/DragNDrop.pm:232 +msgid "Drag and Drop Help" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:241 +msgid "" +"Drag items in the universal set to copy them to a list. Tab and shift-tab " +"can be used to focus universal set items. A focused item in the universal " +"set can be added to the first list with the right or down arrow keys, or " +"added to the last list with the left or up arrow keys. A focused item in a " +"list can be removed by using the left or right arrow key until it returns to " +"the universal set." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:235 +msgid "" +"Drag to reorganize items within lists or to move items to a different list. " +"Tab and shift-tab can be used to focus list items. The left and right arrow " +"keys move a focused list item to the list to the left or right. The up and " +"down arrow keys move a focused list item up and down inside a list." +msgstr "" + #: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" @@ -97,7 +165,7 @@ msgstr "" msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:64 msgid "Graded" msgstr "" @@ -105,11 +173,11 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1305 msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1304 msgid "Hint:" msgstr "" @@ -117,20 +185,40 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1113 +#: /opt/webwork/pg/macros/PG.pl:1115 /opt/webwork/pg/macros/PG.pl:1132 msgid "Incorrect" msgstr "오답" +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:227 +msgid "Item %1 in the universal set added as item %2 to list %3." +msgstr "" + +#. ('%1s', '%2s') +#: /opt/webwork/pg/lib/DragNDrop.pm:228 +msgid "Item %1 removed from list %2." +msgstr "" + +#. ('%1s', '%2s', '%3s', '%4s') +#: /opt/webwork/pg/lib/DragNDrop.pm:231 +msgid "Moved item %1 in list %2 to item %3 in list %4." +msgstr "" + +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:229 +msgid "Moved item %1 in list %2 to item %3." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:139 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1285 +#: /opt/webwork/pg/macros/PG.pl:1300 msgid "Preview of Your Answer" msgstr "" @@ -139,7 +227,19 @@ msgstr "" msgid "Quick Entry" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1328 +#: /opt/webwork/pg/lib/DragNDrop.pm:222 +msgid "Remove" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:220 +msgid "Reset" +msgstr "" + +#: /opt/webwork/pg/macros/graph/AppletObjects.pl:115 +msgid "Return this question to its initial state" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1343 msgid "Reveal" msgstr "" @@ -147,11 +247,11 @@ msgstr "" msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1297 msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1296 msgid "Solution:" msgstr "" @@ -159,40 +259,69 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "다음 단계로 이동하려면 답변을 다시 제출하십시오." -#: /opt/webwork/pg/macros/PG.pl:1375 +#: /opt/webwork/pg/macros/PG.pl:1390 msgid "The answer has been graded." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1393 +#: /opt/webwork/pg/macros/PG.pl:1408 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1364 +#: /opt/webwork/pg/macros/PG.pl:1379 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1374 +#: /opt/webwork/pg/macros/PG.pl:1389 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1384 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1284 +msgid "The degrees of the vertices in this graph are not all even." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1399 msgid "The question has not been answered." msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1110 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1671 msgid "This answer was marked correct because the primary answer is correct." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:85 msgid "This answer will be graded at a later time." msgstr "" +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1307 +msgid "" +"This graph does not have two vertices of odd degree and all other vertices " +"of even degree." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1376 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1383 +msgid "This graph has a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1281 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1294 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1377 +msgid "This graph is not connected." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1331 +msgid "This path does not traverse all edges." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1343 +msgid "This path is not a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3099 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -204,18 +333,21 @@ msgstr "" msgid "True" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:62 msgid "Ungraded" msgstr "채점되지 않은" -#: /opt/webwork/pg/macros/PG.pl:1280 -msgid "You Entered" +#: /opt/webwork/pg/lib/DragNDrop.pm:225 +msgid "Universal Set" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1128 +msgid "Unknown" msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 -msgid "You can earn partial credit on this problem." -msgstr "이 문제의 부분 점수를 받았습니다." +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "You Entered" +msgstr "" #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." @@ -225,7 +357,7 @@ msgstr "이 문제의 새 버전은 마감일 이후에 받을 수 있습니다. msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3092 msgid "Your browser does not support the video tag." msgstr "" @@ -233,71 +365,71 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#. ($name) #. ($1) +#. ($name) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:650 msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2977 msgid "end image description" msgstr "" +# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -# does not need to be translated #: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 #: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:818 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2893 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2975 msgid "image description" msgstr "" +#. ($i + 1) #. (1) #. ($count) -#. ($i + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:558 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:567 msgid "option %1 " msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:821 msgid "otherwise" msgstr "" -#. ($2 + 1) #. ($radioIndex) +#. ($2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:652 msgid "part %1 " msgstr "" #. ($1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:645 msgid "problem %1 " msgstr "" #. ($1 + 1, $2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:658 msgid "row %1 column %2 " msgstr "" #. ($partIndex) -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:653 msgid "subpart %1 " msgstr "" diff --git a/lib/WeBWorK/PG/Localize/pg.pot b/lib/WeBWorK/PG/Localize/pg.pot index f7d4fdab2b..40430370f7 100644 --- a/lib/WeBWorK/PG/Localize/pg.pot +++ b/lib/WeBWorK/PG/Localize/pg.pot @@ -14,64 +14,122 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" +#. ($edgeText[0], $edgeText[1]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:813 +msgid " There are edges between %1 and %2." +msgstr "" + +#. (join($comma, @edgeText[ 0 .. $#edgeText - 1 ]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:817 +msgid " There are edges between %1%2and %3." +msgstr "" + +#. ($edgeText[0]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:811 +msgid " There is an edge between %1." +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:806 +msgid "%1 and %2" +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:803 +msgid "%1 and %2 with weight %3" +msgstr "" + #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1455 +#: /opt/webwork/pg/macros/PG.pl:1470 msgid "%1 of the answers %plural(%1,has,have) been graded." msgstr "" #. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1427 +#: /opt/webwork/pg/macros/PG.pl:1442 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1451 +#: /opt/webwork/pg/macros/PG.pl:1466 msgid "%1 of the answers will be graded later." msgstr "" #. ($options{resultTitle}) -#: /opt/webwork/pg/macros/PG.pl:1224 +#: /opt/webwork/pg/macros/PG.pl:1239 msgid "%1 with message" msgstr "" #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1117 +#: /opt/webwork/pg/macros/PG.pl:1119 msgid "%1% correct" msgstr "" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1440 +#: /opt/webwork/pg/macros/PG.pl:1455 msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1414 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:596 /opt/webwork/pg/macros/math/SimpleGraph.pl:793 +msgid ", " +msgstr "" + +#. ($self->labelsString) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:791 +msgid "A graph with vertices %1." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:221 +msgid "Add Bucket" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1429 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1405 +#: /opt/webwork/pg/macros/PG.pl:1420 msgid "All of the computer gradable answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1060 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1323 +msgid "An edge traversed by this path does not exist in the graph." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1062 msgid "Answer Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +#: /opt/webwork/pg/macros/PG.pl:1255 /opt/webwork/pg/macros/PG.pl:1331 msgid "Close" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +#: /opt/webwork/pg/lib/DragNDrop.pm:233 +msgid "Close Help" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2908 msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1109 +#: /opt/webwork/pg/macros/PG.pl:1111 msgid "Correct" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1322 +#: /opt/webwork/pg/macros/PG.pl:1337 msgid "Correct Answer" msgstr "" +#: /opt/webwork/pg/lib/DragNDrop.pm:232 +msgid "Drag and Drop Help" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:241 +msgid "Drag items in the universal set to copy them to a list. Tab and shift-tab can be used to focus universal set items. A focused item in the universal set can be added to the first list with the right or down arrow keys, or added to the last list with the left or up arrow keys. A focused item in a list can be removed by using the left or right arrow key until it returns to the universal set." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:235 +msgid "Drag to reorganize items within lists or to move items to a different list. Tab and shift-tab can be used to focus list items. The left and right arrow keys move a focused list item to the list to the left or right. The up and down arrow keys move a focused list item up and down inside a list." +msgstr "" + #: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" @@ -88,7 +146,7 @@ msgstr "" msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:64 msgid "Graded" msgstr "" @@ -96,11 +154,11 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1305 msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1304 msgid "Hint:" msgstr "" @@ -108,19 +166,39 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1113 +#: /opt/webwork/pg/macros/PG.pl:1115 /opt/webwork/pg/macros/PG.pl:1132 msgid "Incorrect" msgstr "" +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:227 +msgid "Item %1 in the universal set added as item %2 to list %3." +msgstr "" + +#. ('%1s', '%2s') +#: /opt/webwork/pg/lib/DragNDrop.pm:228 +msgid "Item %1 removed from list %2." +msgstr "" + +#. ('%1s', '%2s', '%3s', '%4s') +#: /opt/webwork/pg/lib/DragNDrop.pm:231 +msgid "Moved item %1 in list %2 to item %3 in list %4." +msgstr "" + +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:229 +msgid "Moved item %1 in list %2 to item %3." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:139 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1285 +#: /opt/webwork/pg/macros/PG.pl:1300 msgid "Preview of Your Answer" msgstr "" @@ -128,7 +206,19 @@ msgstr "" msgid "Quick Entry" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1328 +#: /opt/webwork/pg/lib/DragNDrop.pm:222 +msgid "Remove" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:220 +msgid "Reset" +msgstr "" + +#: /opt/webwork/pg/macros/graph/AppletObjects.pl:115 +msgid "Return this question to its initial state" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1343 msgid "Reveal" msgstr "" @@ -136,11 +226,11 @@ msgstr "" msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1297 msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1296 msgid "Solution:" msgstr "" @@ -148,39 +238,63 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1375 +#: /opt/webwork/pg/macros/PG.pl:1390 msgid "The answer has been graded." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1393 +#: /opt/webwork/pg/macros/PG.pl:1408 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1364 +#: /opt/webwork/pg/macros/PG.pl:1379 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1374 +#: /opt/webwork/pg/macros/PG.pl:1389 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1384 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1284 +msgid "The degrees of the vertices in this graph are not all even." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1399 msgid "The question has not been answered." msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1110 /opt/webwork/pg/macros/core/PGanswermacros.pl:1671 msgid "This answer was marked correct because the primary answer is correct." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:85 msgid "This answer will be graded at a later time." msgstr "" +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1307 +msgid "This graph does not have two vertices of odd degree and all other vertices of even degree." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1376 /opt/webwork/pg/macros/math/SimpleGraph.pl:1383 +msgid "This graph has a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1281 /opt/webwork/pg/macros/math/SimpleGraph.pl:1294 /opt/webwork/pg/macros/math/SimpleGraph.pl:1377 +msgid "This graph is not connected." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1331 +msgid "This path does not traverse all edges." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1343 +msgid "This path is not a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3099 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -192,16 +306,20 @@ msgstr "" msgid "True" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:62 msgid "Ungraded" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1280 -msgid "You Entered" +#: /opt/webwork/pg/lib/DragNDrop.pm:225 +msgid "Universal Set" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1128 +msgid "Unknown" msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 -msgid "You can earn partial credit on this problem." +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "You Entered" msgstr "" #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 @@ -212,7 +330,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3092 msgid "Your browser does not support the video tag." msgstr "" @@ -220,13 +338,13 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#. ($name) #. ($1) -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +#. ($name) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:650 msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2977 msgid "end image description" msgstr "" @@ -237,43 +355,43 @@ msgstr "" msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:818 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2893 /opt/webwork/pg/macros/core/PGbasicmacros.pl:2975 msgid "image description" msgstr "" +#. ($i + 1) #. (1) #. ($count) -#. ($i + 1) -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:558 /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:567 msgid "option %1 " msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:821 msgid "otherwise" msgstr "" -#. ($2 + 1) #. ($radioIndex) -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +#. ($2 + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:652 msgid "part %1 " msgstr "" #. ($1) -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:645 msgid "problem %1 " msgstr "" #. ($1 + 1, $2 + 1) -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:658 msgid "row %1 column %2 " msgstr "" #. ($partIndex) -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:653 msgid "subpart %1 " msgstr "" diff --git a/lib/WeBWorK/PG/Localize/ru-RU.po b/lib/WeBWorK/PG/Localize/ru-RU.po index a5f146afe1..6b54396cff 100644 --- a/lib/WeBWorK/PG/Localize/ru-RU.po +++ b/lib/WeBWorK/PG/Localize/ru-RU.po @@ -2,82 +2,153 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# # Translators: # Glenn Rice, 2023 -# +# msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: 2023-11-19 04:33+0000\n" "Last-Translator: Glenn Rice, 2023\n" -"Language-Team: Russian (Russia) (https://app.transifex.com/webwork/teams/16644/ru_RU/)\n" +"Language-Team: Russian (Russia) (https://app.transifex.com/webwork/teams/" +"16644/ru_RU/)\n" +"Language: ru_RU\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: ru_RU\n" -"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || " +"(n%100>=11 && n%100<=14)? 2 : 3);\n" + +#. ($edgeText[0], $edgeText[1]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:813 +msgid " There are edges between %1 and %2." +msgstr "" + +#. (join($comma, @edgeText[ 0 .. $#edgeText - 1 ]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:817 +msgid " There are edges between %1%2and %3." +msgstr "" + +#. ($edgeText[0]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:811 +msgid " There is an edge between %1." +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:806 +msgid "%1 and %2" +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:803 +msgid "%1 and %2 with weight %3" +msgstr "" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1455 +#: /opt/webwork/pg/macros/PG.pl:1470 msgid "%1 of the answers %plural(%1,has,have) been graded." msgstr "" #. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1427 +#: /opt/webwork/pg/macros/PG.pl:1442 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1451 +#: /opt/webwork/pg/macros/PG.pl:1466 msgid "%1 of the answers will be graded later." msgstr "" #. ($options{resultTitle}) -#: /opt/webwork/pg/macros/PG.pl:1224 +#: /opt/webwork/pg/macros/PG.pl:1239 msgid "%1 with message" msgstr "" #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1117 +#: /opt/webwork/pg/macros/PG.pl:1119 msgid "%1% correct" msgstr "" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1440 -msgid "" -"%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:596 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:793 +msgid ", " +msgstr "" + +#. ($self->labelsString) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:791 +msgid "A graph with vertices %1." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1414 +#: /opt/webwork/pg/lib/DragNDrop.pm:221 +msgid "Add Bucket" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1429 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1405 +#: /opt/webwork/pg/macros/PG.pl:1420 msgid "All of the computer gradable answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1060 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1323 +msgid "An edge traversed by this path does not exist in the graph." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1062 msgid "Answer Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +#: /opt/webwork/pg/macros/PG.pl:1255 /opt/webwork/pg/macros/PG.pl:1331 msgid "Close" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +#: /opt/webwork/pg/lib/DragNDrop.pm:233 +msgid "Close Help" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2908 msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1109 +#: /opt/webwork/pg/macros/PG.pl:1111 msgid "Correct" msgstr "Верно" -#: /opt/webwork/pg/macros/PG.pl:1322 +#: /opt/webwork/pg/macros/PG.pl:1337 msgid "Correct Answer" msgstr "Правильные ответы" +#: /opt/webwork/pg/lib/DragNDrop.pm:232 +msgid "Drag and Drop Help" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:241 +msgid "" +"Drag items in the universal set to copy them to a list. Tab and shift-tab " +"can be used to focus universal set items. A focused item in the universal " +"set can be added to the first list with the right or down arrow keys, or " +"added to the last list with the left or up arrow keys. A focused item in a " +"list can be removed by using the left or right arrow key until it returns to " +"the universal set." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:235 +msgid "" +"Drag to reorganize items within lists or to move items to a different list. " +"Tab and shift-tab can be used to focus list items. The left and right arrow " +"keys move a focused list item to the list to the left or right. The up and " +"down arrow keys move a focused list item up and down inside a list." +msgstr "" + #: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" @@ -97,7 +168,7 @@ msgstr "" msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:64 msgid "Graded" msgstr "" @@ -105,11 +176,11 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1305 msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1304 msgid "Hint:" msgstr "" @@ -117,20 +188,40 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1113 +#: /opt/webwork/pg/macros/PG.pl:1115 /opt/webwork/pg/macros/PG.pl:1132 msgid "Incorrect" msgstr "" +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:227 +msgid "Item %1 in the universal set added as item %2 to list %3." +msgstr "" + +#. ('%1s', '%2s') +#: /opt/webwork/pg/lib/DragNDrop.pm:228 +msgid "Item %1 removed from list %2." +msgstr "" + +#. ('%1s', '%2s', '%3s', '%4s') +#: /opt/webwork/pg/lib/DragNDrop.pm:231 +msgid "Moved item %1 in list %2 to item %3 in list %4." +msgstr "" + +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:229 +msgid "Moved item %1 in list %2 to item %3." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:139 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1285 +#: /opt/webwork/pg/macros/PG.pl:1300 msgid "Preview of Your Answer" msgstr "" @@ -139,7 +230,19 @@ msgstr "" msgid "Quick Entry" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1328 +#: /opt/webwork/pg/lib/DragNDrop.pm:222 +msgid "Remove" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:220 +msgid "Reset" +msgstr "" + +#: /opt/webwork/pg/macros/graph/AppletObjects.pl:115 +msgid "Return this question to its initial state" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1343 msgid "Reveal" msgstr "" @@ -147,11 +250,11 @@ msgstr "" msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1297 msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1296 msgid "Solution:" msgstr "" @@ -159,40 +262,69 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1375 +#: /opt/webwork/pg/macros/PG.pl:1390 msgid "The answer has been graded." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1393 +#: /opt/webwork/pg/macros/PG.pl:1408 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1364 +#: /opt/webwork/pg/macros/PG.pl:1379 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1374 +#: /opt/webwork/pg/macros/PG.pl:1389 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1384 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1284 +msgid "The degrees of the vertices in this graph are not all even." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1399 msgid "The question has not been answered." msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1110 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1671 msgid "This answer was marked correct because the primary answer is correct." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:85 msgid "This answer will be graded at a later time." msgstr "" +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1307 +msgid "" +"This graph does not have two vertices of odd degree and all other vertices " +"of even degree." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1376 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1383 +msgid "This graph has a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1281 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1294 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1377 +msgid "This graph is not connected." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1331 +msgid "This path does not traverse all edges." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1343 +msgid "This path is not a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3099 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -204,18 +336,21 @@ msgstr "" msgid "True" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:62 msgid "Ungraded" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1280 -msgid "You Entered" +#: /opt/webwork/pg/lib/DragNDrop.pm:225 +msgid "Universal Set" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1128 +msgid "Unknown" msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 -msgid "You can earn partial credit on this problem." -msgstr "Балл за эту задачу может дробиться. " +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "You Entered" +msgstr "" #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." @@ -225,7 +360,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3092 msgid "Your browser does not support the video tag." msgstr "" @@ -233,71 +368,71 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#. ($name) #. ($1) +#. ($name) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:650 msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2977 msgid "end image description" msgstr "" +# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -# does not need to be translated #: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 #: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:818 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2893 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2975 msgid "image description" msgstr "" +#. ($i + 1) #. (1) #. ($count) -#. ($i + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:558 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:567 msgid "option %1 " msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:821 msgid "otherwise" msgstr "" -#. ($2 + 1) #. ($radioIndex) +#. ($2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:652 msgid "part %1 " msgstr "" #. ($1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:645 msgid "problem %1 " msgstr "" #. ($1 + 1, $2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:658 msgid "row %1 column %2 " msgstr "" #. ($partIndex) -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:653 msgid "subpart %1 " msgstr "" diff --git a/lib/WeBWorK/PG/Localize/tr.po b/lib/WeBWorK/PG/Localize/tr.po index bc31070d4f..8fabfdf7b2 100644 --- a/lib/WeBWorK/PG/Localize/tr.po +++ b/lib/WeBWorK/PG/Localize/tr.po @@ -2,10 +2,10 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# # Translators: # Glenn Rice, 2023 -# +# msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" @@ -13,71 +13,139 @@ msgstr "" "PO-Revision-Date: 2023-11-19 04:33+0000\n" "Last-Translator: Glenn Rice, 2023\n" "Language-Team: Turkish (https://app.transifex.com/webwork/teams/16644/tr/)\n" +"Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: tr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" +#. ($edgeText[0], $edgeText[1]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:813 +msgid " There are edges between %1 and %2." +msgstr "" + +#. (join($comma, @edgeText[ 0 .. $#edgeText - 1 ]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:817 +msgid " There are edges between %1%2and %3." +msgstr "" + +#. ($edgeText[0]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:811 +msgid " There is an edge between %1." +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:806 +msgid "%1 and %2" +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:803 +msgid "%1 and %2 with weight %3" +msgstr "" + #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1455 +#: /opt/webwork/pg/macros/PG.pl:1470 msgid "%1 of the answers %plural(%1,has,have) been graded." msgstr "" #. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1427 +#: /opt/webwork/pg/macros/PG.pl:1442 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1451 +#: /opt/webwork/pg/macros/PG.pl:1466 msgid "%1 of the answers will be graded later." msgstr "" #. ($options{resultTitle}) -#: /opt/webwork/pg/macros/PG.pl:1224 +#: /opt/webwork/pg/macros/PG.pl:1239 msgid "%1 with message" msgstr "" #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1117 +#: /opt/webwork/pg/macros/PG.pl:1119 msgid "%1% correct" msgstr "%1% doğru" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1440 -msgid "" -"%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "Soruların %1 tanesi yanıtsız bırakıldı." -#: /opt/webwork/pg/macros/PG.pl:1414 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:596 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:793 +msgid ", " +msgstr "" + +#. ($self->labelsString) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:791 +msgid "A graph with vertices %1." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:221 +msgid "Add Bucket" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1429 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1405 +#: /opt/webwork/pg/macros/PG.pl:1420 msgid "All of the computer gradable answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1060 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1323 +msgid "An edge traversed by this path does not exist in the graph." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1062 msgid "Answer Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +#: /opt/webwork/pg/macros/PG.pl:1255 /opt/webwork/pg/macros/PG.pl:1331 msgid "Close" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +#: /opt/webwork/pg/lib/DragNDrop.pm:233 +msgid "Close Help" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2908 msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1109 +#: /opt/webwork/pg/macros/PG.pl:1111 msgid "Correct" msgstr "Doğru yanıt" -#: /opt/webwork/pg/macros/PG.pl:1322 +#: /opt/webwork/pg/macros/PG.pl:1337 msgid "Correct Answer" msgstr "" +#: /opt/webwork/pg/lib/DragNDrop.pm:232 +msgid "Drag and Drop Help" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:241 +msgid "" +"Drag items in the universal set to copy them to a list. Tab and shift-tab " +"can be used to focus universal set items. A focused item in the universal " +"set can be added to the first list with the right or down arrow keys, or " +"added to the last list with the left or up arrow keys. A focused item in a " +"list can be removed by using the left or right arrow key until it returns to " +"the universal set." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:235 +msgid "" +"Drag to reorganize items within lists or to move items to a different list. " +"Tab and shift-tab can be used to focus list items. The left and right arrow " +"keys move a focused list item to the list to the left or right. The up and " +"down arrow keys move a focused list item up and down inside a list." +msgstr "" + #: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" @@ -97,7 +165,7 @@ msgstr "" msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:64 msgid "Graded" msgstr "" @@ -105,11 +173,11 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1305 msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1304 msgid "Hint:" msgstr "" @@ -117,20 +185,40 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1113 +#: /opt/webwork/pg/macros/PG.pl:1115 /opt/webwork/pg/macros/PG.pl:1132 msgid "Incorrect" msgstr "" +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:227 +msgid "Item %1 in the universal set added as item %2 to list %3." +msgstr "" + +#. ('%1s', '%2s') +#: /opt/webwork/pg/lib/DragNDrop.pm:228 +msgid "Item %1 removed from list %2." +msgstr "" + +#. ('%1s', '%2s', '%3s', '%4s') +#: /opt/webwork/pg/lib/DragNDrop.pm:231 +msgid "Moved item %1 in list %2 to item %3 in list %4." +msgstr "" + +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:229 +msgid "Moved item %1 in list %2 to item %3." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:139 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1285 +#: /opt/webwork/pg/macros/PG.pl:1300 msgid "Preview of Your Answer" msgstr "" @@ -139,7 +227,19 @@ msgstr "" msgid "Quick Entry" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1328 +#: /opt/webwork/pg/lib/DragNDrop.pm:222 +msgid "Remove" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:220 +msgid "Reset" +msgstr "" + +#: /opt/webwork/pg/macros/graph/AppletObjects.pl:115 +msgid "Return this question to its initial state" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1343 msgid "Reveal" msgstr "" @@ -147,11 +247,11 @@ msgstr "" msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1297 msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1296 msgid "Solution:" msgstr "" @@ -159,40 +259,69 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1375 +#: /opt/webwork/pg/macros/PG.pl:1390 msgid "The answer has been graded." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1393 +#: /opt/webwork/pg/macros/PG.pl:1408 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1364 +#: /opt/webwork/pg/macros/PG.pl:1379 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1374 +#: /opt/webwork/pg/macros/PG.pl:1389 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1384 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1284 +msgid "The degrees of the vertices in this graph are not all even." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1399 msgid "The question has not been answered." msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1110 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1671 msgid "This answer was marked correct because the primary answer is correct." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:85 msgid "This answer will be graded at a later time." msgstr "" +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1307 +msgid "" +"This graph does not have two vertices of odd degree and all other vertices " +"of even degree." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1376 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1383 +msgid "This graph has a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1281 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1294 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1377 +msgid "This graph is not connected." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1331 +msgid "This path does not traverse all edges." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1343 +msgid "This path is not a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3099 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -204,18 +333,21 @@ msgstr "" msgid "True" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:62 msgid "Ungraded" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1280 -msgid "You Entered" +#: /opt/webwork/pg/lib/DragNDrop.pm:225 +msgid "Universal Set" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1128 +msgid "Unknown" msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 -msgid "You can earn partial credit on this problem." -msgstr "Bu sorudan kısmi puan alabilirsiniz." +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "You Entered" +msgstr "" #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." @@ -225,7 +357,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3092 msgid "Your browser does not support the video tag." msgstr "" @@ -233,71 +365,71 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#. ($name) #. ($1) +#. ($name) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:650 msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2977 msgid "end image description" msgstr "" +# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -# does not need to be translated #: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 #: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:818 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2893 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2975 msgid "image description" msgstr "" +#. ($i + 1) #. (1) #. ($count) -#. ($i + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:558 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:567 msgid "option %1 " msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:821 msgid "otherwise" msgstr "" -#. ($2 + 1) #. ($radioIndex) +#. ($2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:652 msgid "part %1 " msgstr "" #. ($1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:645 msgid "problem %1 " msgstr "" #. ($1 + 1, $2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:658 msgid "row %1 column %2 " msgstr "" #. ($partIndex) -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:653 msgid "subpart %1 " msgstr "" diff --git a/lib/WeBWorK/PG/Localize/zh-CN.po b/lib/WeBWorK/PG/Localize/zh-CN.po index 5d25da03d7..9069fe9792 100644 --- a/lib/WeBWorK/PG/Localize/zh-CN.po +++ b/lib/WeBWorK/PG/Localize/zh-CN.po @@ -2,82 +2,151 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# # Translators: # Glenn Rice, 2023 -# +# msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: 2023-11-19 04:33+0000\n" "Last-Translator: Glenn Rice, 2023\n" -"Language-Team: Chinese (China) (https://app.transifex.com/webwork/teams/16644/zh_CN/)\n" +"Language-Team: Chinese (China) (https://app.transifex.com/webwork/teams/" +"16644/zh_CN/)\n" +"Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: zh_CN\n" "Plural-Forms: nplurals=1; plural=0;\n" +#. ($edgeText[0], $edgeText[1]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:813 +msgid " There are edges between %1 and %2." +msgstr "" + +#. (join($comma, @edgeText[ 0 .. $#edgeText - 1 ]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:817 +msgid " There are edges between %1%2and %3." +msgstr "" + +#. ($edgeText[0]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:811 +msgid " There is an edge between %1." +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:806 +msgid "%1 and %2" +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:803 +msgid "%1 and %2 with weight %3" +msgstr "" + #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1455 +#: /opt/webwork/pg/macros/PG.pl:1470 msgid "%1 of the answers %plural(%1,has,have) been graded." msgstr "" #. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1427 +#: /opt/webwork/pg/macros/PG.pl:1442 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1451 +#: /opt/webwork/pg/macros/PG.pl:1466 msgid "%1 of the answers will be graded later." msgstr "" #. ($options{resultTitle}) -#: /opt/webwork/pg/macros/PG.pl:1224 +#: /opt/webwork/pg/macros/PG.pl:1239 msgid "%1 with message" msgstr "" #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1117 +#: /opt/webwork/pg/macros/PG.pl:1119 msgid "%1% correct" msgstr "%1% 正确" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1440 -msgid "" -"%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "%quant(%1,个问题,个问题) 未完成。" -#: /opt/webwork/pg/macros/PG.pl:1414 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:596 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:793 +msgid ", " +msgstr "" + +#. ($self->labelsString) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:791 +msgid "A graph with vertices %1." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:221 +msgid "Add Bucket" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1429 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1405 +#: /opt/webwork/pg/macros/PG.pl:1420 msgid "All of the computer gradable answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1060 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1323 +msgid "An edge traversed by this path does not exist in the graph." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1062 msgid "Answer Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +#: /opt/webwork/pg/macros/PG.pl:1255 /opt/webwork/pg/macros/PG.pl:1331 msgid "Close" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +#: /opt/webwork/pg/lib/DragNDrop.pm:233 +msgid "Close Help" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2908 msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1109 +#: /opt/webwork/pg/macros/PG.pl:1111 msgid "Correct" msgstr "正确" -#: /opt/webwork/pg/macros/PG.pl:1322 +#: /opt/webwork/pg/macros/PG.pl:1337 msgid "Correct Answer" msgstr "" +#: /opt/webwork/pg/lib/DragNDrop.pm:232 +msgid "Drag and Drop Help" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:241 +msgid "" +"Drag items in the universal set to copy them to a list. Tab and shift-tab " +"can be used to focus universal set items. A focused item in the universal " +"set can be added to the first list with the right or down arrow keys, or " +"added to the last list with the left or up arrow keys. A focused item in a " +"list can be removed by using the left or right arrow key until it returns to " +"the universal set." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:235 +msgid "" +"Drag to reorganize items within lists or to move items to a different list. " +"Tab and shift-tab can be used to focus list items. The left and right arrow " +"keys move a focused list item to the list to the left or right. The up and " +"down arrow keys move a focused list item up and down inside a list." +msgstr "" + #: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" @@ -97,7 +166,7 @@ msgstr "" msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:64 msgid "Graded" msgstr "" @@ -105,11 +174,11 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1305 msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1304 msgid "Hint:" msgstr "" @@ -117,20 +186,40 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1113 +#: /opt/webwork/pg/macros/PG.pl:1115 /opt/webwork/pg/macros/PG.pl:1132 msgid "Incorrect" msgstr "" +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:227 +msgid "Item %1 in the universal set added as item %2 to list %3." +msgstr "" + +#. ('%1s', '%2s') +#: /opt/webwork/pg/lib/DragNDrop.pm:228 +msgid "Item %1 removed from list %2." +msgstr "" + +#. ('%1s', '%2s', '%3s', '%4s') +#: /opt/webwork/pg/lib/DragNDrop.pm:231 +msgid "Moved item %1 in list %2 to item %3 in list %4." +msgstr "" + +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:229 +msgid "Moved item %1 in list %2 to item %3." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:139 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1285 +#: /opt/webwork/pg/macros/PG.pl:1300 msgid "Preview of Your Answer" msgstr "" @@ -139,7 +228,19 @@ msgstr "" msgid "Quick Entry" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1328 +#: /opt/webwork/pg/lib/DragNDrop.pm:222 +msgid "Remove" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:220 +msgid "Reset" +msgstr "" + +#: /opt/webwork/pg/macros/graph/AppletObjects.pl:115 +msgid "Return this question to its initial state" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1343 msgid "Reveal" msgstr "" @@ -147,11 +248,11 @@ msgstr "" msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1297 msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1296 msgid "Solution:" msgstr "" @@ -159,40 +260,69 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1375 +#: /opt/webwork/pg/macros/PG.pl:1390 msgid "The answer has been graded." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1393 +#: /opt/webwork/pg/macros/PG.pl:1408 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1364 +#: /opt/webwork/pg/macros/PG.pl:1379 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1374 +#: /opt/webwork/pg/macros/PG.pl:1389 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1384 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1284 +msgid "The degrees of the vertices in this graph are not all even." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1399 msgid "The question has not been answered." msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1110 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1671 msgid "This answer was marked correct because the primary answer is correct." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:85 msgid "This answer will be graded at a later time." msgstr "" +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1307 +msgid "" +"This graph does not have two vertices of odd degree and all other vertices " +"of even degree." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1376 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1383 +msgid "This graph has a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1281 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1294 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1377 +msgid "This graph is not connected." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1331 +msgid "This path does not traverse all edges." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1343 +msgid "This path is not a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3099 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -204,18 +334,21 @@ msgstr "" msgid "True" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:62 msgid "Ungraded" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1280 -msgid "You Entered" +#: /opt/webwork/pg/lib/DragNDrop.pm:225 +msgid "Universal Set" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1128 +msgid "Unknown" msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 -msgid "You can earn partial credit on this problem." -msgstr "这一题你可以得到部分成绩" +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "You Entered" +msgstr "" #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." @@ -225,7 +358,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3092 msgid "Your browser does not support the video tag." msgstr "" @@ -233,71 +366,71 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#. ($name) #. ($1) +#. ($name) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:650 msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2977 msgid "end image description" msgstr "" +# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -# does not need to be translated #: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 #: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:818 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2893 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2975 msgid "image description" msgstr "" +#. ($i + 1) #. (1) #. ($count) -#. ($i + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:558 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:567 msgid "option %1 " msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:821 msgid "otherwise" msgstr "" -#. ($2 + 1) #. ($radioIndex) +#. ($2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:652 msgid "part %1 " msgstr "" #. ($1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:645 msgid "problem %1 " msgstr "" #. ($1 + 1, $2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:658 msgid "row %1 column %2 " msgstr "" #. ($partIndex) -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:653 msgid "subpart %1 " msgstr "" diff --git a/lib/WeBWorK/PG/Localize/zh-HK.po b/lib/WeBWorK/PG/Localize/zh-HK.po index 5dbbc3057f..3093a20f66 100644 --- a/lib/WeBWorK/PG/Localize/zh-HK.po +++ b/lib/WeBWorK/PG/Localize/zh-HK.po @@ -2,82 +2,151 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# # Translators: # Glenn Rice, 2023 -# +# msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: 2023-11-19 04:33+0000\n" "Last-Translator: Glenn Rice, 2023\n" -"Language-Team: Chinese (Hong Kong) (https://app.transifex.com/webwork/teams/16644/zh_HK/)\n" +"Language-Team: Chinese (Hong Kong) (https://app.transifex.com/webwork/teams/" +"16644/zh_HK/)\n" +"Language: zh_HK\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: zh_HK\n" "Plural-Forms: nplurals=1; plural=0;\n" +#. ($edgeText[0], $edgeText[1]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:813 +msgid " There are edges between %1 and %2." +msgstr "" + +#. (join($comma, @edgeText[ 0 .. $#edgeText - 1 ]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:817 +msgid " There are edges between %1%2and %3." +msgstr "" + +#. ($edgeText[0]) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:811 +msgid " There is an edge between %1." +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:806 +msgid "%1 and %2" +msgstr "" + +#. ($self->vertexLabel($i) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:803 +msgid "%1 and %2 with weight %3" +msgstr "" + #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1455 +#: /opt/webwork/pg/macros/PG.pl:1470 msgid "%1 of the answers %plural(%1,has,have) been graded." msgstr "" #. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1427 +#: /opt/webwork/pg/macros/PG.pl:1442 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1451 +#: /opt/webwork/pg/macros/PG.pl:1466 msgid "%1 of the answers will be graded later." msgstr "" #. ($options{resultTitle}) -#: /opt/webwork/pg/macros/PG.pl:1224 +#: /opt/webwork/pg/macros/PG.pl:1239 msgid "%1 with message" msgstr "" #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1117 +#: /opt/webwork/pg/macros/PG.pl:1119 msgid "%1% correct" msgstr "%1% 正确" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1440 -msgid "" -"%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:596 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:793 +msgid ", " +msgstr "" + +#. ($self->labelsString) +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:791 +msgid "A graph with vertices %1." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1414 +#: /opt/webwork/pg/lib/DragNDrop.pm:221 +msgid "Add Bucket" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1429 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1405 +#: /opt/webwork/pg/macros/PG.pl:1420 msgid "All of the computer gradable answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1060 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1323 +msgid "An edge traversed by this path does not exist in the graph." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1062 msgid "Answer Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +#: /opt/webwork/pg/macros/PG.pl:1255 /opt/webwork/pg/macros/PG.pl:1331 msgid "Close" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +#: /opt/webwork/pg/lib/DragNDrop.pm:233 +msgid "Close Help" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2908 msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1109 +#: /opt/webwork/pg/macros/PG.pl:1111 msgid "Correct" msgstr "正确" -#: /opt/webwork/pg/macros/PG.pl:1322 +#: /opt/webwork/pg/macros/PG.pl:1337 msgid "Correct Answer" msgstr "" +#: /opt/webwork/pg/lib/DragNDrop.pm:232 +msgid "Drag and Drop Help" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:241 +msgid "" +"Drag items in the universal set to copy them to a list. Tab and shift-tab " +"can be used to focus universal set items. A focused item in the universal " +"set can be added to the first list with the right or down arrow keys, or " +"added to the last list with the left or up arrow keys. A focused item in a " +"list can be removed by using the left or right arrow key until it returns to " +"the universal set." +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:235 +msgid "" +"Drag to reorganize items within lists or to move items to a different list. " +"Tab and shift-tab can be used to focus list items. The left and right arrow " +"keys move a focused list item to the list to the left or right. The up and " +"down arrow keys move a focused list item up and down inside a list." +msgstr "" + #: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" @@ -97,7 +166,7 @@ msgstr "" msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:64 msgid "Graded" msgstr "" @@ -105,11 +174,11 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1305 msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1304 msgid "Hint:" msgstr "" @@ -117,20 +186,40 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1113 +#: /opt/webwork/pg/macros/PG.pl:1115 /opt/webwork/pg/macros/PG.pl:1132 msgid "Incorrect" msgstr "" +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:227 +msgid "Item %1 in the universal set added as item %2 to list %3." +msgstr "" + +#. ('%1s', '%2s') +#: /opt/webwork/pg/lib/DragNDrop.pm:228 +msgid "Item %1 removed from list %2." +msgstr "" + +#. ('%1s', '%2s', '%3s', '%4s') +#: /opt/webwork/pg/lib/DragNDrop.pm:231 +msgid "Moved item %1 in list %2 to item %3 in list %4." +msgstr "" + +#. ('%1s', '%2s', '%3s') +#: /opt/webwork/pg/lib/DragNDrop.pm:229 +msgid "Moved item %1 in list %2 to item %3." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:139 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1285 +#: /opt/webwork/pg/macros/PG.pl:1300 msgid "Preview of Your Answer" msgstr "" @@ -139,7 +228,19 @@ msgstr "" msgid "Quick Entry" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1328 +#: /opt/webwork/pg/lib/DragNDrop.pm:222 +msgid "Remove" +msgstr "" + +#: /opt/webwork/pg/lib/DragNDrop.pm:220 +msgid "Reset" +msgstr "" + +#: /opt/webwork/pg/macros/graph/AppletObjects.pl:115 +msgid "Return this question to its initial state" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1343 msgid "Reveal" msgstr "" @@ -147,11 +248,11 @@ msgstr "" msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1297 msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1296 msgid "Solution:" msgstr "" @@ -159,40 +260,69 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1375 +#: /opt/webwork/pg/macros/PG.pl:1390 msgid "The answer has been graded." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1393 +#: /opt/webwork/pg/macros/PG.pl:1408 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1364 +#: /opt/webwork/pg/macros/PG.pl:1379 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1374 +#: /opt/webwork/pg/macros/PG.pl:1389 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1384 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1284 +msgid "The degrees of the vertices in this graph are not all even." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1399 msgid "The question has not been answered." msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1110 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1671 msgid "This answer was marked correct because the primary answer is correct." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:85 msgid "This answer will be graded at a later time." msgstr "" +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1307 +msgid "" +"This graph does not have two vertices of odd degree and all other vertices " +"of even degree." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1376 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1383 +msgid "This graph has a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1281 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1294 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1377 +msgid "This graph is not connected." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1331 +msgid "This path does not traverse all edges." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1343 +msgid "This path is not a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3099 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -204,18 +334,21 @@ msgstr "" msgid "True" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:62 msgid "Ungraded" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1280 -msgid "You Entered" +#: /opt/webwork/pg/lib/DragNDrop.pm:225 +msgid "Universal Set" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1128 +msgid "Unknown" msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 -msgid "You can earn partial credit on this problem." -msgstr "这一题你可以得到部分成绩" +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "You Entered" +msgstr "" #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." @@ -225,7 +358,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3092 msgid "Your browser does not support the video tag." msgstr "" @@ -233,71 +366,71 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#. ($name) #. ($1) +#. ($name) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:650 msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2977 msgid "end image description" msgstr "" +# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -# does not need to be translated #: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 #: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:818 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2893 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2975 msgid "image description" msgstr "" +#. ($i + 1) #. (1) #. ($count) -#. ($i + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 -#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:558 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:567 msgid "option %1 " msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:821 msgid "otherwise" msgstr "" -#. ($2 + 1) #. ($radioIndex) +#. ($2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:652 msgid "part %1 " msgstr "" #. ($1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:645 msgid "problem %1 " msgstr "" #. ($1 + 1, $2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:658 msgid "row %1 column %2 " msgstr "" #. ($partIndex) -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:653 msgid "subpart %1 " msgstr "" diff --git a/lib/WeBWorK/PG/SampleProblemParser.pm b/lib/WeBWorK/PG/SampleProblemParser.pm new file mode 100644 index 0000000000..42fd73ffc3 --- /dev/null +++ b/lib/WeBWorK/PG/SampleProblemParser.pm @@ -0,0 +1,474 @@ +package WeBWorK::PG::SampleProblemParser; +use parent qw(Exporter); + +use strict; +use warnings; +use experimental 'signatures'; +use feature 'say'; + +my $pgRoot; + +use Mojo::File qw(curfile); +BEGIN { $pgRoot = curfile->dirname->dirname->dirname->dirname; } + +use File::Basename qw(dirname basename); +use File::Find qw(find); +use Mojo::File qw(path); +use Mojo::JSON qw(decode_json encode_json); +use Pandoc; +use Pod::Simple::Search; +use Pod::Simple::SimpleTree; + +our @EXPORT_OK = qw(parseSampleProblem generateMetadata getSampleProblemCode getSearchData); + +=head1 NAME + +WeBWorK::PG::SampleProblemParser - Parse sample problems and extract metadata, +documentation, and code. + +=head2 parseSampleProblem + +Parse a PG file with extra documentation comments. The input is the file and a +hash of global variables: + +=over + +=item * + +C: A reference to a hash which has information (name, directory, +types, subjects, categories) of every sample problem file. + +=item * + +C: A reference to a hash of macros to include as links within a +problem. + +=item * + +C: The base URL for the POD HTML files. + +=item * + +C: The base URL for the sample problem HTML files. + +=item * + +C: The html url extension (including the dot) to use for pg doc +links. The default is the empty string. + +=back + +=cut + +sub parseSampleProblem ($file, %global) { + my $filename = basename($file); + open(my $FH, '<:encoding(UTF-8)', $file) or do { + warn qq{Could not open file "$file": $!}; + return {}; + }; + my @file_contents = <$FH>; + close $FH; + + my (@blocks, @doc_rows, @code_rows, @description); + my (%options, $descr, $type, $name); + + $global{url_extension} //= ''; + + while (my $row = shift @file_contents) { + chomp($row); + $row =~ s/\t/ /g; + if ($row =~ /^#:%\s*(categor(y|ies)|types?|subjects?|see_also|name)\s*=\s*(.*)\s*$/) { + # skip this, already parsed. + } elsif ($row =~ /^#:%\s*(.*)?/) { + # The row has the form #:% section = NAME. + # This should parse the previous named section and then reset @doc_rows and @code_rows. + push( + @blocks, + { + %options, + doc => pandoc->convert(markdown => 'html', join("\n", @doc_rows)), + code => join("\n", @code_rows) + } + ) if %options; + %options = split(/\s*:\s*|\s*,\s*|\s*=\s*|\s+/, $1); + @doc_rows = (); + @code_rows = (); + } elsif ($row =~ /^#:/) { + # This section is documentation to be parsed. + $row = $row =~ s/^#:\s?//r; + + # Parse any LINK/PODLINK/PROBLINK commands in the documentation. + if ($row =~ /(POD|PROB)?LINK\('(.*?)'\s*(,\s*'(.*)')?\)/) { + my $link_text = defined($1) ? $1 eq 'POD' ? $2 : $global{metadata}{$2}{name} : $2; + my $url = + defined($1) + ? $1 eq 'POD' + ? "$global{pod_base_url}/" . $global{macro_locations}{ $4 // $2 } + : "$global{sample_problem_base_url}/$global{metadata}{$2}{dir}/" + . ($2 =~ s/.pg$/$global{url_extension}/r) + : $4; + $row = $row =~ s/(POD|PROB)?LINK\('(.*?)'\s*(,\s*'(.*)')?\)/[$link_text]($url)/gr; + } + + push(@doc_rows, $row); + } elsif ($row =~ /^##\s*(END)?DESCRIPTION\s*$/) { + $descr = $1 ? 0 : 1; + } elsif ($row =~ /^##/ && $descr) { + push(@description, $row =~ s/^##\s*//r); + push(@code_rows, $row); + } else { + push(@code_rows, $row); + } + } + + # The last @doc_rows must be parsed then added to the @blocks. + push( + @blocks, + { + %options, + doc => pandoc->convert(markdown => 'html', join("\n", @doc_rows)), + code => join("\n", @code_rows) + } + ); + + return { + name => $global{metadata}{$filename}{name}, + blocks => \@blocks, + code => join("\n", map { $_->{code} } @blocks), + description => join("\n", @description) + }; +} + +=head2 generateMetadata + +Build a hash of metadata for all PG files in the given directory. A reference +to the hash that is built is returned. + +=cut + +sub generateMetadata ($problem_dir, %options) { + my $index_table = {}; + + find( + { + wanted => sub { + say "Reading file: $File::Find::name" if $options{verbose}; + + if ($File::Find::name =~ /\.pg$/) { + my $metadata = parseMetadata($File::Find::name, $problem_dir); + unless (@{ $metadata->{types} }) { + warn "The type of sample problem is missing for $File::Find::name."; + return; + } + unless ($metadata->{name}) { + warn "The name attribute is missing for $File::Find::name."; + return; + } + $index_table->{ basename($File::Find::name) } = $metadata; + } + } + }, + $problem_dir + ); + + return $index_table; +} + +my @macros_to_skip = qw( + PGML.pl + PGcourse.pl + PGstandard.pl +); + +sub parseMetadata ($path, $problem_dir) { + open(my $FH, '<:encoding(UTF-8)', $path) or do { + warn qq{Could not open file "$path": $!}; + return {}; + }; + my @file_contents = <$FH>; + close $FH; + + my @problem_types = qw(sample technique snippet); + + my $metadata = { dir => (dirname($path) =~ s/$problem_dir\/?//r) =~ s/\/*$//r }; + + while (my $row = shift @file_contents) { + if ($row =~ /^#:%\s*(categor(y|ies)|types?|subjects?|see_also|name)\s*=\s*(.*)\s*$/) { + # The row has the form #:% categories = [cat1, cat2, ...]. + my $label = lc($1); + my @opts = $3 =~ /\[(.*)\]/ ? map { $_ =~ s/^\s*|\s*$//r } split(/,/, $1) : ($3); + if ($label =~ /types?/) { + for my $opt (@opts) { + warn "The type of problem must be one of @problem_types" + unless grep { lc($opt) eq $_ } @problem_types; + } + $metadata->{types} = [ map { lc($_) } @opts ]; + } elsif ($label =~ /^categor/) { + $metadata->{categories} = \@opts; + } elsif ($label =~ /^subject/) { + $metadata->{subjects} = [ map { lc($_) } @opts ]; + } elsif ($label eq 'name') { + $metadata->{name} = $opts[0]; + } elsif ($label eq 'see_also') { + $metadata->{related} = \@opts; + } + } elsif ($row =~ /loadMacros\(/) { + chomp($row); + # Parse the macros, which may be on multiple rows. + my $macros = $row; + while ($row && $row !~ /\);\s*$/) { + $row = shift @file_contents; + chomp($row); + $macros .= $row; + } + # Split by commas and pull out the quotes. + my @macros = map {s/['"\s]//gr} split(/\s*,\s*/, $macros =~ s/loadMacros\((.*)\)\;$/$1/r); + $metadata->{macros} = []; + for my $macro (@macros) { + push(@{ $metadata->{macros} }, $macro) unless grep { $_ eq $macro } @macros_to_skip; + } + } + } + + return $metadata; +} + +=head2 getSampleProblemCode + +Parse a PG file with extra documentation comments and strip that all out +returning the clean problem code. This returns the same code that +C returns, except at much less expense as it does not parse +the documentation, it does not require that the metadata be parsed first, and it +does not need macro POD information. + +=cut + +sub getSampleProblemCode ($file) { + my $filename = basename($file); + open(my $FH, '<:encoding(UTF-8)', $file) or do { + warn qq{Could not open file "$file": $!}; + return ''; + }; + my @file_contents = <$FH>; + close $FH; + + my (@code_rows, $inCode); + + while (my $row = shift @file_contents) { + chomp($row); + $row =~ s/\t/ /g; + if ($row =~ /^#:(.*)?/) { + # This is documentation so skip it. + } elsif ($row =~ /^\s*(END)?DOCUMENT.*$/) { + $inCode = $1 ? 0 : 1; + push(@code_rows, $row); + } elsif ($inCode) { + push(@code_rows, $row); + } + } + + return join("\n", @code_rows); +} + +=head2 getSearchData + +Generate search data for sample problem files and macro POD. The only argument +is required and should be a file name to write the search data to. If the file +does not exist, then a new file containing the generated search data will be +written. If the file exists and contains search data from previously using this +method, then the data will be updated based on file modification times of the +sample problem files and macros. In any case an array reference containing the +generated search data will be returned. + +=cut + +my $stopWordsCache; + +sub getSearchData ($searchDataFileName) { + my $searchDataFile = path($searchDataFileName); + my %files = map { $_->{filename} => $_ } @{ (eval { decode_json($searchDataFile->slurp('UTF-8')) } // []) }; + my @updatedFiles; + + my $stopWords = sub ($word) { + return $stopWordsCache->{$word} if $stopWordsCache; + $stopWordsCache = {}; + + my $contents = eval { path("$pgRoot/assets/stop-words-en.txt")->slurp('UTF-8') }; + return $stopWordsCache if $@; + + for my $line (split("\n", $contents)) { + chomp $line; + next if $line =~ /^#/ || !$line; + $stopWordsCache->{$line} = 1; + } + + return $stopWordsCache->{$word}; + }; + + my $processLine = sub ($line) { + my %words; + + # Extract linked macros and problems. + my @linkedFiles = $line =~ /(?:PODLINK|PROBLINK)\('([\w.]+)'\)/g; + $words{$_} = 1 for @linkedFiles; + + # Replace any non-word characters with spaces. + $line =~ s/\W/ /g; + + for my $word (split(/\s+/, $line)) { + next if $word =~ /^\d*$/; + $word = lc($word); + $words{$word} = 1 if !$stopWords->($word); + } + return keys %words; + }; + + # Extract the text for a section from the given POD with a section header title. + my $extractHeadText = sub ($root, $title) { + my @index = grep { ref($root->[$_]) eq 'ARRAY' && $root->[$_][2] eq $title } 0 .. $#$root; + return unless @index == 1; + + my $node = $root->[ $index[0] + 1 ]; + my $str = ''; + for (2 .. $#$node) { + $str .= ref($node->[$_]) eq 'ARRAY' ? $node->[$_][2] : $node->[$_]; + } + return $str; + }; + + # Extract terms form POD headers. + my $extractHeaders = sub ($root) { + my %terms = + map { $_ => 1 } + grep { $_ && !$stopWords->($_) } + map { split(/\s+/, $_) } + map { lc($_) =~ s/\W/ /gr } + map { + grep { !ref($_) } + @$_[ 2 .. $#$_ ] + } grep { ref($_) eq 'ARRAY' && $_->[0] =~ /^head\d+$/ } @$root; + return [ keys %terms ]; + }; + + # Process the sample problems in the sample problem directory. + find( + { + wanted => sub { + return unless $_ =~ /\.pg$/; + + my $file = path($File::Find::name); + my $lastModified = $file->stat->mtime; + + if ($files{$_}) { + push(@updatedFiles, $files{$_}); + return if $files{$_}{lastModified} >= $lastModified; + } + + my @fileContents = eval { split("\n", $file->slurp('UTF-8')) }; + return if $@; + + if (!$files{$_}) { + $files{$_} = { + type => 'sample problem', + filename => $_, + dir => $file->dirname->basename + }; + push(@updatedFiles, $files{$_}); + } + $files{$_}{lastModified} = $lastModified; + + my (%words, @kw, @macros, @subjects, $description); + + while (@fileContents) { + my $line = shift @fileContents; + if ($line =~ /^#:%\s*(\w+)\s*=\s*(.*)\s*$/) { + # Store the name and subjects. + $files{$_}{name} = $2 if $1 eq 'name'; + if ($1 eq 'subject') { + @subjects = split(',\s*', $2 =~ s/\[(.*)\]/$1/r); + } + } elsif ($line =~ /^#:\s*(.*)?/) { + my @newWords = $processLine->($1); + @words{@newWords} = (1) x @newWords if @newWords; + } elsif ($line =~ /loadMacros\(/) { + my $macros = $line; + while ($line && $line !~ /\);\s*$/) { + $line = shift @fileContents; + $macros .= $line; + } + my @usedMacros = + map {s/['"\s]//gr} split(/\s*,\s*/, $macros =~ s/loadMacros\((.*)\)\;$/$1/r); + + # Get the macros other than PGML.pl, PGstandard.pl, and PGcourse.pl. + for my $m (@usedMacros) { + push(@macros, $m) unless $m =~ /^(PGML|PGstandard|PGcourse)\.pl$/; + } + } elsif ($line =~ /##\s*KEYWORDS\((.*)\)/) { + @kw = map {s/^'(.*)'$/$1/r} split(/,\s*/, $1); + } elsif ($line =~ /^##\s*DESCRIPTION/) { + $line = shift(@fileContents); + while ($line && $line !~ /^##\s*ENDDESCRIPTION/) { + $description .= ($line =~ s/^##\s+//r) . ' '; + $line = shift(@fileContents); + } + $description =~ s/\s+$//; + } + } + + $files{$_}{description} = $description; + $files{$_}{subjects} = \@subjects; + $files{$_}{terms} = [ keys %words ]; + $files{$_}{keywords} = \@kw; + $files{$_}{macros} = \@macros; + + return; + } + }, + "$pgRoot/tutorial/sample-problems" + ); + + # Process the POD in macros in the macros directory. + (undef, my $macroFiles) = Pod::Simple::Search->new->inc(0)->survey("$pgRoot/macros"); + for my $macroFile (sort keys %$macroFiles) { + next if $macroFile =~ /deprecated/; + + my $file = path($macroFile); + my $fileName = $file->basename; + my $lastModified = $file->stat->mtime; + + if ($files{$fileName}) { + push(@updatedFiles, $files{$fileName}); + next if $files{$fileName}{lastModified} >= $lastModified; + } + + if (!$files{$fileName}) { + $files{$fileName} = { + type => 'macro', + id => scalar(keys %files) + 1, + filename => $fileName, + dir => $file->dirname->to_rel($pgRoot)->to_string + }; + push(@updatedFiles, $files{$fileName}); + } + $files{$fileName}{lastModified} = $lastModified; + + my $root = Pod::Simple::SimpleTree->new->parse_file($file->to_string)->root; + + $files{$fileName}{terms} = $extractHeaders->($root); + + if (my $nameDescription = $extractHeadText->($root, 'NAME')) { + (undef, my $description) = split(/\s*-\s*/, $nameDescription, 2); + $files{$fileName}{description} = $description if $description; + } + } + + # Re-index in case files were added or removed. + my $count = 0; + $_->{id} = ++$count for @updatedFiles; + + $searchDataFile->spew(encode_json(\@updatedFiles), 'UTF-8'); + + return \@updatedFiles; +} + +1; diff --git a/lib/WeBWorK/PG/Tidy.pm b/lib/WeBWorK/PG/Tidy.pm index 0a747776ca..4bb8ae36ac 100644 --- a/lib/WeBWorK/PG/Tidy.pm +++ b/lib/WeBWorK/PG/Tidy.pm @@ -81,7 +81,7 @@ my $postfilter = sub { $evalString =~ s/(.*)->tex\(<BEGIN_LATEX_IMAGE/g; # Care is needed to reverse the preprocessing here. - # First in all occurences of an odd number of backslashes the last backslash is replaced with two tildes. + # First in all occurrences of an odd number of backslashes the last backslash is replaced with two tildes. $evalString =~ s/(?source_string($source); # provide the source string for the problem # or - $pt->source_file($sourceFilePath); # provide the proble file containing the source + $pt->source_file($sourceFilePath); # provide the problem file containing the source # Load the unprotected macro files. # These files are evaluated with the Safe compartment wide open. @@ -477,7 +477,7 @@ Specifically the following are allowed: time - Gives the current Unix time. atan, sin, cos, exp, log, sqrt - - Arithemetic commands. More are defined in PGauxiliaryFunctions.pl + - Arithmetic commands. More are defined in PGauxiliaryFunctions.pl The following are specifically not allowed: @@ -1077,9 +1077,6 @@ sub avg_problem_grader { my %problem_result = (score => 0, errors => '', type => 'avg_problem_grader', msg => ''); - $problem_result{msg} = eval('main::maketext("You can earn partial credit on this problem.")') - if keys %$answers > 1; - # Return unless answers have been submitted. return (\%problem_result, $problem_state) unless $form_options{answers_submitted} == 1; diff --git a/lib/WeBWorK/Utils/PODParser.pm b/lib/WeBWorK/Utils/PODParser.pm new file mode 100644 index 0000000000..57950d46b0 --- /dev/null +++ b/lib/WeBWorK/Utils/PODParser.pm @@ -0,0 +1,66 @@ +package WeBWorK::Utils::PODParser; +use parent qw(Pod::Simple::XHTML); + +use strict; +use warnings; + +use Pod::Simple::XHTML; +use File::Basename qw(basename); + +# $podFiles must be provided in order for pod links to local files to work. It should be the +# first return value of the POD::Simple::Search survey method. +sub new { + my ($invocant, $podFiles) = @_; + my $class = ref $invocant || $invocant; + my $self = $class->SUPER::new(@_); + $self->perldoc_url_prefix('https://metacpan.org/pod/'); + $self->index(1); + $self->backlink(1); + $self->html_charset('UTF-8'); + $self->{podFiles} = $podFiles // {}; + return bless $self, $class; +} + +# Attempt to resolve links to local files. If a local file is not found, then +# let Pod::Simple::XHTML resolve to a cpan link. +sub resolve_pod_page_link { + my ($self, $target, $section) = @_; + + unless (defined $target) { + print "Using internal page link.\n" if $self->{verbose} > 2; + return $self->SUPER::resolve_pod_page_link($target, $section); + } + + my $podFound; + for (keys %{ $self->{podFiles} }) { + if ($target eq $_ =~ s/lib:://r || $target eq basename($self->{podFiles}{$_}) =~ s/\.pod$//r) { + $podFound = + $self->{assert_html_ext} ? ($self->{podFiles}{$_} =~ s/\.(pm|pl|pod)$/.html/r) : $self->{podFiles}{$_}; + last; + } + } + + if ($podFound) { + my $pod_url = $self->encode_entities($podFound =~ s/^$self->{source_root}/$self->{base_url}/r) + . ($section ? '#' . $self->idify($self->encode_entities($section), 1) : ''); + print "Resolved local pod link for $target" . ($section ? "/$section" : '') . " to $pod_url\n" + if $self->{verbose} > 2; + return $pod_url; + } + + print "Using cpan pod link for $target" . ($section ? "/$section" : '') . "\n" if $self->{verbose} > 2; + return $self->SUPER::resolve_pod_page_link($target, $section); +} + +# Trim spaces from the beginning of each line in code blocks. This attempts to +# trim spaces from all lines in the code block in the same amount as there are +# spaces at the beginning of the first line. Note that Pod::Simple::XHTML has +# already converted tab characters into 8 spaces. +sub handle_code { + my ($self, $code) = @_; + my $start_spaces = length(($code =~ /^( *)/)[0]) || ''; + $self->SUPER::handle_code($code =~ s/^( {1,$start_spaces})//gmr); + return; +} + +1; diff --git a/lib/WeBWorK/Utils/PODtoHTML.pm b/lib/WeBWorK/Utils/PODtoHTML.pm new file mode 100644 index 0000000000..19c30e7f50 --- /dev/null +++ b/lib/WeBWorK/Utils/PODtoHTML.pm @@ -0,0 +1,212 @@ +package WeBWorK::Utils::PODtoHTML; + +use strict; +use warnings; +use utf8; + +use Pod::Simple::Search; +use Mojo::Template; +use Mojo::DOM; +use Mojo::Collection qw(c); +use File::Path qw(make_path); +use File::Basename qw(dirname); +use IO::File; +use POSIX qw(strftime); + +use WeBWorK::Utils::PODParser; + +our @sections = ( + doc => 'Documentation', + bin => 'Scripts', + macros => 'Macros', + lib => 'Libraries', +); +our %macro_names = ( + answers => 'Answers', + contexts => 'Contexts', + core => 'Core', + deprecated => 'Deprecated', + graph => 'Graph', + math => 'Math', + misc => 'Miscellaneous', + parsers => 'Parsers', + ui => 'User Interface' +); + +sub new { + my ($invocant, %o) = @_; + my $class = ref $invocant || $invocant; + + my @section_list = ref($o{sections}) eq 'ARRAY' ? @{ $o{sections} } : @sections; + my $section_hash = {@section_list}; + my $section_order = [ map { $section_list[ 2 * $_ ] } 0 .. $#section_list / 2 ]; + delete $o{sections}; + + my $self = { + %o, + idx => {}, + section_hash => $section_hash, + section_order => $section_order, + macros_hash => {}, + }; + return bless $self, $class; +} + +sub convert_pods { + my $self = shift; + my $source_root = $self->{source_root}; + my $dest_root = $self->{dest_root}; + + my $regex = join('|', map {"^$_"} @{ $self->{section_order} }); + + my ($name2path, $path2name) = Pod::Simple::Search->new->inc(0)->limit_re(qr!$regex!)->survey($self->{source_root}); + for (keys %$path2name) { + print "Processing file: $_\n" if $self->{verbose} > 1; + $self->process_pod($_, $name2path); + } + + $self->write_index("$dest_root/index.html"); + + return; +} + +sub process_pod { + my ($self, $pod_path, $pod_files) = @_; + + my $pod_name; + + my ($subdir, $filename) = $pod_path =~ m|^$self->{source_root}/(?:(.*)/)?(.*)$|; + + my ($subdir_first, $subdir_rest) = ('', ''); + + if (defined $subdir) { + if ($subdir =~ m|/|) { + ($subdir_first, $subdir_rest) = $subdir =~ m|^([^/]*)/(.*)|; + } else { + $subdir_first = $subdir; + } + } + + $pod_name = (defined $subdir_rest ? "$subdir_rest/" : '') . $filename; + if ($filename =~ /\.pl$/) { + $filename =~ s/\.pl$/.html/; + } elsif ($filename =~ /\.pod$/) { + $pod_name =~ s/\.pod$//; + $filename =~ s/\.pod$/.html/; + } elsif ($filename =~ /\.pm$/) { + $pod_name =~ s/\.pm$//; + $pod_name =~ s|/+|::|g; + $filename =~ s/\.pm$/.html/; + } elsif ($filename !~ /\.html$/) { + $filename .= '.html'; + } + + $pod_name =~ s/^(\/|::)//; + + my $html_dir = $self->{dest_root} . (defined $subdir ? "/$subdir" : ''); + my $html_path = "$html_dir/$filename"; + my $html_rel_path = defined $subdir ? "$subdir/$filename" : $filename; + + $self->update_index($subdir, $html_rel_path, $pod_name); + make_path($html_dir); + my $html = $self->do_pod2html( + pod_path => $pod_path, + pod_name => $pod_name, + pod_files => $pod_files + ); + my $fh = IO::File->new($html_path, '>:encoding(UTF-8)') + or die "Failed to open file '$html_path' for writing: $!\n"; + print $fh $html; + + return; +} + +sub update_index { + my ($self, $subdir, $html_rel_path, $pod_name) = @_; + + $subdir =~ s|/.*$||; + my $idx = $self->{idx}; + my $sections = $self->{section_hash}; + if ($subdir eq 'macros') { + $idx->{macros} = []; + if ($pod_name =~ m!^(.+)/(.+)$!) { + push @{ $self->{macros_hash}{$1} }, [ $html_rel_path, $2 ]; + } else { + push @{ $idx->{doc} }, [ $html_rel_path, $pod_name ]; + } + } elsif (exists $sections->{$subdir}) { + push @{ $idx->{$subdir} }, [ $html_rel_path, $pod_name ]; + } else { + warn "no section for subdir '$subdir'\n"; + } + + return; +} + +sub write_index { + my ($self, $out_path) = @_; + + my $fh = IO::File->new($out_path, '>:encoding(UTF-8)') or die "Failed to open index '$out_path' for writing: $!\n"; + print $fh Mojo::Template->new(vars => 1)->render_file( + "$self->{template_dir}/category-index.mt", + { + title => 'POD for ' . ($self->{source_root} =~ s|^.*/||r), + dest_url => $self->{dest_url}, + home_url => $self->{home_url}, + home_url_link_name => $self->{home_url_link_name}, + pod_index => $self->{idx}, + sections => $self->{section_hash}, + section_order => $self->{section_order}, + macros => $self->{macros_hash}, + macros_order => [ sort keys %{ $self->{macros_hash} } ], + macro_names => \%macro_names, + date => strftime('%a %b %e %H:%M:%S %Z %Y', localtime) + } + ); + + return; +} + +sub do_pod2html { + my ($self, %o) = @_; + + my $psx = WeBWorK::Utils::PODParser->new($o{pod_files}); + $psx->{source_root} = $self->{source_root}; + $psx->{verbose} = $self->{verbose}; + $psx->{assert_html_ext} = 1; + $psx->{base_url} = $self->{page_url} // $self->{dest_url} // ''; + $psx->output_string(\my $html); + $psx->html_header(''); + $psx->html_footer(''); + $psx->parse_file($o{pod_path}); + + my $dom = Mojo::DOM->new($html); + my $podIndexUL = $dom->at('ul[id="index"]'); + my $podIndex = $podIndexUL ? $podIndexUL->find('ul[id="index"] > li') : c(); + for (@$podIndex) { + $_->attr({ class => 'nav-item' }); + $_->at('a')->attr({ class => 'nav-link p-0' }); + for (@{ $_->find('ul') }) { + $_->attr({ class => 'nav flex-column w-100' }); + } + for (@{ $_->find('li') }) { + $_->attr({ class => 'nav-item' }); + $_->at('a')->attr({ class => 'nav-link p-0' }); + } + } + my $podHTML = $podIndexUL ? $podIndexUL->remove : $html; + + return Mojo::Template->new(vars => 1)->render_file( + "$self->{template_dir}/pod.mt", + { + title => $o{pod_name}, + dest_url => $self->{dest_url}, + home_url => $self->{home_url}, + home_url_link_name => $self->{home_url_link_name}, + index => $podIndex, + content => $podHTML + } + ); +} + +1; diff --git a/macros/PG.pl b/macros/PG.pl index ef398570db..5573a1ec34 100644 --- a/macros/PG.pl +++ b/macros/PG.pl @@ -7,7 +7,7 @@ =head1 SYNOPSIS In a PG problem: - DOCUMENT(); # should be the first statment in the problem + DOCUMENT(); # should be the first statement in the problem loadMacros(.....); # (optional) load other macro files if needed. @@ -20,7 +20,7 @@ =head1 SYNOPSIS # It is defined in PGbasicmacros.pl. ); - ANS(answer_evalutors); # see PGanswermacros.pl for examples of answer evaluatiors. + ANS(answer_evaluators); # see PGanswermacros.pl for examples of answer evaluatiors. ENDDOCUMENT() # must be the last statement in the problem @@ -111,14 +111,13 @@ =head2 DOCUMENT sub DOCUMENT { # get environment - $rh_envir = \%envir; #KLUDGE FIXME - # warn "rh_envir is ",ref($rh_envir); - $PG = new PGcore( - $rh_envir, # can add key/value options to modify + $rh_envir = \%envir; #KLUDGE FIXME + + $PG = new PGcore( + $rh_envir, # can add key/value options to modify ); - $PG->clear_internal_debug_messages; - # initialize main:: variables + # initialize main:: variables $ANSWER_PREFIX = $PG->{ANSWER_PREFIX}; $QUIZ_PREFIX = $PG->{QUIZ_PREFIX}; $showPartialCorrectAnswers = $PG->{flags}->{showPartialCorrectAnswers}; @@ -398,9 +397,7 @@ () } sub AskSage { - my $python = shift; - my $options = shift; - WARN_MESSAGE("the second argument to AskSage should be a hash of options") unless $options =~ /HASH/; + my ($python, $options) = @_; $PG->AskSage($python, $options); } @@ -415,7 +412,7 @@ sub sageReturnedFail { =head2 NAMED_ANS -Associates answer names with answer evaluators. If the given anwer name has a +Associates answer names with answer evaluators. If the given answer name has a response group in the PG_ANSWERS_HASH, then the evaluator is added to that response group. Otherwise the name and evaluator are added to the hash of explicitly named answer evaluators. They will be paired with exlplicitly @@ -554,7 +551,7 @@ sub persistent_data { } # The store_persistent_data, update_persistent_data, and get_persistent_data methods are deprecated and are only still -# here for backward compatability. Use the persistent_data method instead which can do everything these three methods +# here for backward compatibility. Use the persistent_data method instead which can do everything these three methods # can do. Note that if you use the persistent_data method, then you will need to join the values as strings if you want # that. Even better pass the persistent_data method an array reference containing the values so you can avoid the hassle # of splitting the values when they are retrieved. @@ -620,7 +617,7 @@ sub NEW_ANS_ARRAY_NAME_EXTENSION { } my $ans_label = $PG->new_ans_name(); my $element_ans_label = $PG->new_array_element_label($ans_label, $row_num, $col_num, vec_num => $vecnum); - my $response = new PGresponsegroup($ans_label, $element_ans_label, undef); + my $response = PGresponsegroup->new($ans_label, $element_ans_label, undef); $PG->extend_ans_group($ans_label, $response); return $element_ans_label; } @@ -632,7 +629,7 @@ sub CLEAR_RESPONSES { if (ref($responsegroup)) { $responsegroup->clear; } else { - $responsegroup = $PG->{PG_ANSWERS_HASH}{$ans_label}{response} = new PGresponsegroup($label); + $responsegroup = $PG->{PG_ANSWERS_HASH}{$ans_label}{response} = PGresponsegroup->new($ans_label); } } return; @@ -846,8 +843,12 @@ =head2 ENDDOCUMENT =item * -C: This is the bootstrap button class added to the feedback button. -By default it is "btn-info", "btn-success", "btn-danger", or "btn-warning" +C: This is the button class added to the feedback button. These are +based on bootstrap button styles, but are custom styles for the feedback +buttons to allow clients to theme the bootstrap buttons without changing the +feedback styles. By default it is "btn-preview" (copied from btn-info), +"btn-correct" (copied from btn-success), "btn-incorrect" (copied from +btn-danger), or "btn-partially-correct" (copied from btn-warning) depending on the status of the answer and the type of submission. =item * @@ -994,7 +995,8 @@ sub ENDDOCUMENT { if ($ansHash->{correct_value}) { for (keys %{ $ansHash->{correct_value}->context->flag('mathQuillOpts') }) { - $mq_part_opts->{$_} = 0 unless defined $mq_part_opts->{$_}; + $mq_part_opts->{$_} = $ansHash->{correct_value}->context->flag('mathQuillOpts')->{$_} + unless defined $mq_part_opts->{$_}; } } @@ -1059,7 +1061,7 @@ sub ENDDOCUMENT { my %options = ( resultTitle => maketext('Answer Preview'), resultClass => '', - btnClass => 'btn-info', + btnClass => 'btn-preview', btnAddClass => 'ms-2', feedbackElements => Mojo::Collection->new, insertElement => undef, @@ -1108,15 +1110,28 @@ sub ENDDOCUMENT { } elsif ($answerScore >= 1) { $options{resultTitle} = maketext('Correct'); $options{resultClass} = 'correct'; - $options{btnClass} = 'btn-success'; + $options{btnClass} = 'btn-correct'; } elsif ($answerScore == 0) { $options{resultTitle} = maketext('Incorrect'); $options{resultClass} = 'incorrect'; - $options{btnClass} = 'btn-danger'; + $options{btnClass} = 'btn-incorrect'; } else { $options{resultTitle} = maketext('[_1]% correct', round($answerScore * 100)); $options{resultClass} = 'partially-correct'; - $options{btnClass} = 'btn-warning'; + $options{btnClass} = 'btn-partially-correct'; + } + } elsif ($rh_envir->{showAttemptResults} && !$PG->{flags}{showPartialCorrectAnswers}) { + # Partially correct feedback is not being shown, but this is not a preview, so make the + # feedback look different than a preview. If the problem score is zero, everything is + # incorrect, so mark the whole problem as incorrect. Otherwise mark it as unknown correctness. + if ($problemResult->{score}) { + $options{resultTitle} = maketext('Unknown'); + $options{resultClass} = 'unknown'; + $options{btnClass} = 'btn-partially-correct'; + } else { + $options{resultTitle} = maketext('Incorrect'); + $options{resultClass} = 'incorrect'; + $options{btnClass} = 'btn-incorrect'; } } @@ -1490,7 +1505,7 @@ sub ENDDOCUMENT { '', map { '
  • ' . knowlLink($_, value => pretty_print($PG->{PG_alias}{resource_list}{$_})) . '
  • ' - } + } sort keys %{ $PG->{PG_alias}{resource_list} } ) . '' @@ -1781,7 +1796,7 @@ =head2 set_default_options Occasionally one wants to write a filter which accepts a long list of options, not all of which are known in advance, but only uses a subset of the options -provided. In this case, setting C to 1 prevents the error +provided. In this case, setting C to 1 prevents the error from being signaled. =cut diff --git a/macros/README.md b/macros/README.md index e9211e5cc0..692d678835 100644 --- a/macros/README.md +++ b/macros/README.md @@ -3,7 +3,6 @@ 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 @@ -178,7 +177,7 @@ This directory now has a subdirectory structure for clarification. ## Macros fixed - `math/algebraMacros.pl` (removed all code, already in `customizeLaTeX.pl`) -- `parsers/parserUtils.pl` (removed loadMacros call due to errors. ) +- `parsers/parserUtils.pl` (removed loadMacros call due to errors.) - `contexts/contextFiniteSolutionSets.pl` (fixed warnings) - `contexts/contextLimitedRadicalComplex.pl` (fixed warnings) - `contexts/contextRationalExponent.pl` (fixed warnings) diff --git a/macros/answers/PGasu.pl b/macros/answers/PGasu.pl index 781c346b57..642c3850c5 100644 --- a/macros/answers/PGasu.pl +++ b/macros/answers/PGasu.pl @@ -116,7 +116,7 @@ =head2 no_trig_fun 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 +See https://wiki.openwebwork.org/wiki/DisableFunctions =cut @@ -202,7 +202,7 @@ =head2 must_have_filter A similar effect can be accomplished with Contexts() by undefining the trig functions. -See http://webwork.maa.org/wiki/Modifying_contexts_%28advanced%29 +See https://wiki.openwebwork.org/wiki/DisableFunctions =cut @@ -452,7 +452,7 @@ =head2 weighted_partial_grader [DEPRECATED] $ENV{'partial_weights'} = [.2,.2,.2,.3]; -This will soon be superceded by a better grader. +This will soon be superseded by a better grader. =cut diff --git a/macros/answers/PGfunctionevaluators.pl b/macros/answers/PGfunctionevaluators.pl index d8a7910f0b..d0a572996e 100644 --- a/macros/answers/PGfunctionevaluators.pl +++ b/macros/answers/PGfunctionevaluators.pl @@ -30,7 +30,7 @@ =head1 SYNOPSIS BEGIN { strict->import; } -# Until we get the PG cacheing business sorted out, we need to use +# Until we get the PG caching business sorted out, we need to use # PG_restricted_eval to get the correct values for some(?) PG environment # variables. We do this once here and place the values in lexicals for later # access. @@ -126,7 +126,7 @@ =head3 Options abs(studentAnswer - correctAnswer) <= abs(.01*relTol*correctAnswer) -tol and relTol are mutually exclusive. reltol is also accpeted as a synonym for +tol and relTol are mutually exclusive. reltol is also accepted as a synonym for relTol. =item zeroLevel, zeroLevelTol @@ -169,7 +169,7 @@ =head3 Options var=>['t'] var=>'t' -vars is recognied as a synonym for var. The default is a single variable, x. +vars is recognized as a synonym for var. The default is a single variable, x. =item limits @@ -653,7 +653,7 @@ sub FUNCTION_CMP { my $s = ($n != 1) ? "s" : ""; foreach my $p (@{$testPoints}) { $p = [$p] unless ref($p) eq 'ARRAY'; - warn "Test point (" . join(',', @{$p}) . ") should have $n coordiante$s" + warn "Test point (" . join(',', @{$p}) . ") should have $n coordinate$s" unless scalar(@{$p}) == $n; } } diff --git a/macros/answers/PGnumericevaluators.pl b/macros/answers/PGnumericevaluators.pl index c91df50761..6e7c1f2c07 100644 --- a/macros/answers/PGnumericevaluators.pl +++ b/macros/answers/PGnumericevaluators.pl @@ -48,7 +48,7 @@ =head2 MathObjects and answer evaluators BEGIN { strict->import; } -# Until we get the PG cacheing business sorted out, we need to use +# Until we get the PG caching business sorted out, we need to use # PG_restricted_eval to get the correct values for some(?) PG environment # variables. We do this once here and place the values in lexicals for later # access. @@ -135,7 +135,7 @@ =head2 Options strings are of the form '%m.nx' or '%m.nx#', where m and n are described below, and x is a formatter. -Esentially, m is the minimum length of the field (make this negative to +Essentially, m is the minimum length of the field (make this negative to left-justify). Note that the decimal point counts as a character when determining the field width. If m begins with a zero, the number will be padded with zeros instead of spaces to fit the field. @@ -189,7 +189,7 @@ =head2 Options abs(studentAnswer - correctAnswer) <= abs(.01*relTol*correctAnswer) -tol and relTol are mutually exclusive. reltol is also accpeted as a synonym for +tol and relTol are mutually exclusive. reltol is also accepted as a synonym for relTol. =item zeroLevel, zeroLevelTol @@ -257,7 +257,7 @@ sub num_cmp { my %out_options; ######################################################################### - # Retain this first check for backword compatibility. Allows input of the form + # Retain this first check for backward compatibility. Allows input of the form # num_cmp($ans, 1, '%0.5f') but warns against it ######################################################################### my %known_options = ( @@ -419,7 +419,7 @@ sub num_cmp { return (wantarray) ? @output_list : $output_list[0]; } -#legacy code for compatability purposes +#legacy code for compatibility purposes sub num_rel_cmp { # compare numbers std_num_cmp(@_); } @@ -427,7 +427,7 @@ sub num_rel_cmp { # compare numbers =head1 "mode"_num_cmp() functions There are 16 functions that provide simplified interfaces to num_cmp(). They are -organized into four groups, based on the number of answers accpeted (single or +organized into four groups, based on the number of answers accepted (single or list) and whether relative or absolute tolerances are used. Each group contains four functions, one for each evaluation mode. See the mode option to num_cmp() above for details about each mode. @@ -463,7 +463,7 @@ =head2 The list group ANS(frac_num_cmp_list($relTol, $format, @answerList)); This group of functions produces answer evaluators for a list of correct answers -using relative tolerances. $relTol and $format are equivelent to the +using relative tolerances. $relTol and $format are equivalent to the identically-named options to num_cmp() above. @answerList must contain one or more correct answers. A list of answer evaluators is returned, one for each answer provided in @answerList. All answer returned evaluators will use the @@ -489,7 +489,7 @@ =head2 The abs_list group ANS(frac_num_cmp_abs_list($absTol, $format, @answerList)); This group of functions produces answer evaluators for a list of correct answers -using absolute tolerances. $absTol and $format are equivelent to the +using absolute tolerances. $absTol and $format are equivalent to the identically-named options to num_cmp() above. @answerList must contain one or more correct answers. A list of answer evaluators is returned, one for each answer provided in @answerList. All answer returned evaluators will use the @@ -929,7 +929,7 @@ =head2 [DEPRECATED] numerical_compare_with_units ## zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies ## zeroLevelTol -- absolute tolerance to allow when correct answer is close to zero -# This mode is depricated. send input through num_cmp -- it can handle units. +# This mode is deprecated. send input through num_cmp -- it can handle units. sub numerical_compare_with_units { my $correct_answer = shift; # the answer is a string which includes both the numerical answer and the units. @@ -994,7 +994,7 @@ sub NUM_CMP { # low level numeric compare (now uses Parser) if $correctAnswer =~ m/e/ && Value::isNumber($correctAnswer); # - # Get an apppropriate context based on the mode + # Get an appropriate context based on the mode # my $context; for ($mode) { @@ -1232,7 +1232,7 @@ sub ORIGINAL_NUM_CMP { # low level numeric compare ############################################################################### # We'll leave these next lines out for now, so that the evaluated versions of the student's and professor's - # can be displayed in the answer message. This may still cause a few anomolies when strings are used + # can be displayed in the answer message. This may still cause a few anomalies when strings are used # ############################################################################### diff --git a/macros/answers/PGstringevaluators.pl b/macros/answers/PGstringevaluators.pl index 8cdcb259a4..e891c3e668 100644 --- a/macros/answers/PGstringevaluators.pl +++ b/macros/answers/PGstringevaluators.pl @@ -217,7 +217,7 @@ =head3 options A reference to an array of filter names, to be applied to both the correct answer and the student's answer before doing string comparison. Supported -filters are listed above. filter is avaliable as a synonym for filters. +filters are listed above. filter is available as a synonym for filters. =item debug diff --git a/macros/answers/answerHints.pl b/macros/answers/answerHints.pl index e91327d5c5..4c4b775966 100644 --- a/macros/answers/answerHints.pl +++ b/macros/answers/answerHints.pl @@ -24,7 +24,7 @@ =head2 AnswerHints displayed. (See the examples below.) The right-hand side can be either the message string itself, or -a referrence to an array where the first element is the message +a reference to an array where the first element is the message string, and the remaining elements are name-value pairs that set options for the message. These can include: @@ -32,29 +32,32 @@ =head2 AnswerHints =item C 0 or 1 >>> -1 means check for messages even -if the answer is correct. +In the list of incorrect answers to compare the student's answer with, 1 means +for each CODE reference incorrect answer, check the student's answer against +that CODE reference incorrect answer for messages even if the student answer is +correct. Default: 0 =item C 0 or 1 >>> -1 means it's OK to repalce any +1 means it's OK to replace any message that is already in place in the answer hash. Default: 0 =item C 0 or 1 >>> -1 means only perform the test -if the student answer is the -same type as the correct one. +In the list of incorrect answers to compare the student's answer with, 1 means +for each CODE reference incorrect answer, check the student's answer against +that CODE reference incorrect answer for messages only when the the student +answer the same type as the correct answer. Default: 1 =item C 0 or 1 >>> 1 means process student answers even during answer previews. Usually, no -hints are given durring previews, but +hints are given during previews, but only when answers are checked or submitted. The default can be controlled on an individual message basis, or by adding @@ -120,7 +123,6 @@ sub AnswerHints { my $hash = $context->{answerHash}; $context->{answerHash} = $ans; my $processPreview = $correct->getFlag('answerHintsProcessPreview', 0); - $context->{answerHash} = $hash; while (@_) { my $wrongList = shift; @@ -172,6 +174,7 @@ sub AnswerHints { } } } + $context->{answerHash} = $hash; return $ans; }, @_ @@ -191,6 +194,9 @@ sub Compare { $ans->{typeError} = 0; $ans->{ans_message} = $ans->{error_message} = ""; $ans->{score} = 0; + my $context = $self->context; + my $hash = $context->{answerHash}; + $context->{answerHash} = $ans; if ($self->address != $ans->{correct_value}->address) { $ans->{correct_ans} = $self->string; @@ -205,6 +211,7 @@ sub Compare { $self->cmp_preprocess($ans); $self->cmp_equal($ans); $self->cmp_postprocess($ans) if !$ans->{error_message} && !$ans->{typeError}; + $context->{answerHash} = $hash; return $ans->{score} >= 1; } diff --git a/macros/answers/answerVariableList.pl b/macros/answers/answerVariableList.pl index 8871a2e789..a26ea59ef4 100644 --- a/macros/answers/answerVariableList.pl +++ b/macros/answers/answerVariableList.pl @@ -83,7 +83,7 @@ =head2 addVariables addVariables(@vars) -Adds each string in @vars as a varible to the current context. +Adds each string in @vars as a variable to the current context. =cut diff --git a/macros/answers/extraAnswerEvaluators.pl b/macros/answers/extraAnswerEvaluators.pl index f0494a8d6f..7dc1186d43 100644 --- a/macros/answers/extraAnswerEvaluators.pl +++ b/macros/answers/extraAnswerEvaluators.pl @@ -324,7 +324,7 @@ sub interval_cmp { my $ans_type = ''; # set to List, Union, or String below # - # Get an apppropriate context based on the mode + # Get an appropriate context based on the mode # my $oldContext = Context(); my $context = mode2context($mode, %opts); @@ -409,7 +409,7 @@ sub interval_cmp { =head2 number_list_cmp Checks an answer which is a comma-separated list of numbers. The actual -numbers are fed to num_cmp, so all of the flexibilty of num_cmp carries +numbers are fed to num_cmp, so all of the flexibility of num_cmp carries over (values can be expressions to be evaluated). For example, number_list_cmp("1, -2") @@ -428,7 +428,7 @@ =head2 number_list_cmp In cases where you set complex=>'ok', be sure the problem file loads PGcomplexmacros.pl. -Optional arguements for num_cmp (resp. cplx_cmp) can be used as well, +Optional arguments for num_cmp (resp. cplx_cmp) can be used as well, such as number_list_cmp("cos(3), sqrt(111)", relTol => 3) @@ -444,7 +444,7 @@ =head2 number_list_cmp will mark "none" as correct. -One can also specify optionnal arguments for Parser's List checker: showHints, +One can also specify optional arguments for Parser's List checker: showHints, partialCredit, and showLengthHints, as in: number_list_cmp("cos(3), sqrt(111)", partialCredit=>1) @@ -464,7 +464,7 @@ sub number_list_cmp { my %options = (debug => $num_params{debug}); # - # Get an apppropriate context based on the mode + # Get an appropriate context based on the mode # my $oldContext = Context(); my $context = mode2context($mode, %num_params); diff --git a/macros/answers/unorderedAnswer.pl b/macros/answers/unorderedAnswer.pl index 1dfbd65980..06e31385c8 100644 --- a/macros/answers/unorderedAnswer.pl +++ b/macros/answers/unorderedAnswer.pl @@ -25,7 +25,7 @@ =head2 UNORDERED_ANS Collect a group of answer checkers for use with answers that can be given in any order. If C answer checkers are given, then the last C answer -rules will be used. It is beter to use named rules and C +rules will be used. It is better to use named rules and C below. Otherwise, be sure to use C right after the answer rules for the answers you want to compare. diff --git a/macros/capa/PG_CAPAmacros.pl b/macros/capa/PG_CAPAmacros.pl deleted file mode 100644 index f279fe5d1a..0000000000 --- a/macros/capa/PG_CAPAmacros.pl +++ /dev/null @@ -1,208 +0,0 @@ - -BEGIN { strict->import; } - -sub CAPA_ans { - my $ans = shift; - my %options = @_; - my $answer_evaluator = 0; - - #TEXT("answerCounter =". ++$problemCounter,"$BR"); # checking whether values are reinitialized - - # explicitlty delete options which are meaningless to WeBWorK - if (defined($options{'sig'})) { delete($options{'sig'}); } - if (defined($options{'wgt'})) { delete($options{'wgt'}); } - if (defined($options{'tries'})) { delete($options{'tries'}); } - - # $options{'allow_unknown_options'} = 1; ## if uncommented, this is a fast and possibly dangerous - ## way to prevent warning message about unknown options - - if (defined($options{'reltol'}) or defined($options{'tol'}) or defined($options{'unit'})) { - - if (defined($options{'unit'})) { - #$ans = "$ans $options{'unit'}"; - $answer_evaluator = num_cmp( - $ans, - 'format' => $options{format}, - reltol => (defined($options{reltol})) ? $options{reltol} : undef, - tol => (defined($options{tol})) ? $options{tol} : undef, - unit => $options{unit}, - ); - } else { # numerical compare: - if (defined($options{'reltol'})) { #relative tolerance is given with a percent sign - my $reltol = $options{'reltol'}; - my $format = $options{'format'} if defined($options{'format'}); - $answer_evaluator = num_cmp($ans, reltol => $reltol, 'format' => $format); - } elsif (defined($options{'tol'})) { - my $format = $options{'format'} if defined($options{'format'}); - $answer_evaluator = num_cmp($ans, tol => $options{'tol'}, 'format' => $format); - } else { - my $tol = $ans * $main::numRelPercentTolDefault; - my $format = $options{'format'} if defined($options{'format'}); - $answer_evaluator = num_cmp($ans, reltol => $tol, 'format' => $format); - } - } - } else { - # string comparisons - if (defined($options{'str'}) and $options{'str'} =~ /CS/i) { - $answer_evaluator = str_cmp($ans, filters => ['compress_whitespace']); - } elsif (defined($options{'str'}) and $options{'str'} =~ /MC/i) { - $answer_evaluator = str_cmp($ans, filters => [qw( compress_whitespace ignore_case ignore_order )]); - } else { - $answer_evaluator = str_cmp($ans, filters => [qw( compress_whitespace ignore_case )]); - } - } - - $answer_evaluator; -} - -sub CAPA_import { - my $filePath = shift; - my %save_envir = %main::envir; - my $r_string = read_whole_problem_file($filePath); - - $main::envir{'probFileName'} = $filePath; - includePGtext($r_string); - %main::envir = %save_envir; -} - -sub CAPA_map { - my $seed = shift; - my $array_var_ref = shift; - my $PGrand = new PGrandom($seed); - local $main::array_values_ref = shift; # this must be local since it must be passed to PG_restricted_eval - my $size = @$main::array_values_ref; # get number of values - my @array = 0 .. ($size - 1); - my @slice = (); - while (@slice < $size) { - push(@slice, splice(@array, $PGrand->random(0, $#array), 1)); - } - my $string = ""; - my $var; - my $i = 0; - foreach $var (@$array_var_ref) { - $string .= "\$$var = \$\$main::array_values_ref[ $slice[$i++]]; "; - } - - # it is important that PG-restriced eval can accesss the $array_values_ref - my ($val, $PG_eval_errors, $PG_full_error_report) = PG_restricted_eval($string); - my $out = ''; - $string =~ s/\$/\\\$/g; # protect variables for error message - $out = "Error in MAP subroutine: $PG_eval_errors
    \n" . $string . "
    \n" if $PG_eval_errors; - $out; -} - -sub compare_units { - -} - -sub CAPA_hint { - my $hint = shift; - TEXT(hint(qq{ HINT: $hint $main::BR})); -} - -sub CAPA_explanation { - TEXT(solution(qq{ $main::BR$main::BR EXPLANATION: @_ $main::BR$main::BR})) if solution(@_); -} - -sub pow { - my ($base, $exponent) = @_; - $base**$exponent; -} - -sub CAPA_tex { - my $tex = shift; - # $tex =~ s|/*|\$|g; - #$tex =~ s/\\/\\\\/g; #protect backslashes??? - my $nontex = shift; - &M3($tex, $nontex, $nontex); -} - -sub CAPA_web { - my $text = shift; - my $tex = shift; - my $html = shift; - &M3($tex, "\\begin{rawhtml}$html\\end{rawhtml}", $html); -} - -sub CAPA_html { - my $html = shift; - &M3("", "\\begin{rawhtml}$html\\end{rawhtml}", $html); -} - -sub var_in_tex { - my ($tex) = $_[0]; - &M3("$tex", "$tex", ""); -} - -sub isNumberQ { # determine whether the input is a number - my $in = shift; - $in =~ /^[\d\.\+\-Ee]+$/; -} - -sub choose { - # my($in)=join(" ",@_); - my ($var) = $_[0]; - $_[$var]; -} - -sub problem { - $main::probNum; -} - -sub pin { - $main::psvn; -} - -sub section { - $main::sectionNumber; -} - -sub name { - $main::studentName; -} - -sub set { - $main::setNumber; -} - -sub question { - $main::probNum; -} - -sub due_date { - $main::formattedDueDate; -} - -sub answer_date { - $main::formattedAnswerDate; -} - -sub open_date { - $main::formattedOpenDate; -} - -sub to_string { - $_[0]; -} - -sub CAPA_EV { - - my $out = &EV3(@_); - $out =~ s/\n\n/\n/g; # hack to prevent introduction of paragraphs in TeX?? - # HACK TO DO THE RIGHT THING WITH DOLLAR SIGNS - $out = ev_substring($out, "/*/*", "/*/*", \&display_math_ev3); - $out = ev_substring($out, "/*", "/*", \&math_ev3); - # TEXT($main::BR, $main::BR, $out,$main::BR,$main::BR); - $out; -} - -# these are very commonly needed files -CAPA_import("${main::CAPA_Tools}StdMacros"); -CAPA_import("${main::CAPA_Tools}StdUnits"); -CAPA_import("${main::CAPA_Tools}StdConst"); -##################### - -$main::prob_val = ""; # gets rid of spurious errors. -$main::prob_try = ""; - -1; diff --git a/macros/capa/StdConst.pg b/macros/capa/StdConst.pg deleted file mode 100644 index f19fdae6e1..0000000000 --- a/macros/capa/StdConst.pg +++ /dev/null @@ -1,36 +0,0 @@ - -## -## macros for Fundamental Constants, in SI units! -## -## Gravitational constant CapG: Units-> m3/kgs2 or Nm2/kg2 -$CapG = 6.67e-11 ; -## -## Speed of light in m/s: -$smallc = 2.99792458e+8 ; -## -## Electron charge in C: -$smalle = 1.602177e-19 ; -## -## Planck constant, smallh: Units ->Js -$smallh = 6.626075e-34 ; -## -## Boltzmann constant, smallk: Units ->J/K -$smallk = 1.38066e-23 ; -## -## standardgrav. accel sea level, smallg: Units ->m/s2 -$smallg = 9.8 ; - -## -## Pi: -$pi = 3.1415926536 ; -## -## From degrees to radians: -$degrad = $pi / 180.0 ; -## -## From radians to degrees: -$raddeg = 180.0 / $pi ; - -1; -################################################# -## Processing time = 2 secs ( 1.96 usr 0.41 sys = 2.38 cpu) -################################################# diff --git a/macros/capa/StdUnits.pg b/macros/capa/StdUnits.pg deleted file mode 100644 index 80238f24d5..0000000000 --- a/macros/capa/StdUnits.pg +++ /dev/null @@ -1,79 +0,0 @@ - -## Define some macros for the units: -## Note: P means per in this notation, except for Pa =Pascal) -## u is the unit indication -## -$m_u = CAPA_tex( "/*m/*" , "m" ) ; # angle degree -$km_u = CAPA_tex( "/*km/*" , "km" ) ; -$cm_u = CAPA_tex( "/*cm/*" , "cm" ) ; -$mm_u = CAPA_tex( "/*mm/*" , "mm" ) ; -$um_u = CAPA_tex( "/*\micro m/*" , "um" ) ; -$nm_u = CAPA_tex( "/*nm/*" , "nm" ) ; -$fm_u = CAPA_tex( "/*fm/*" , "fm" ) ; -$m2_u = CAPA_tex( "/*m^2/*" , "m^2" ) ; -$m3_u = CAPA_tex( "/*m^3/*" , "m^3" ) ; -$cm2_u = CAPA_tex( "/*cm^2/*" , "cm^2" ) ; -$cm3_u = CAPA_tex( "/*cm^3/*" , "cm^3" ) ; -$mi_u = CAPA_tex( "/*mi/*" , "mi" ) ; -$ft_u = CAPA_tex( "/*ft/*" , "ft" ) ; -$in_u = CAPA_tex( "/*in./*" , "in." ) ; -$kg_u = CAPA_tex( "/*kg/*" , "kg" ) ; -$amu_u = CAPA_tex( "/*u/*" , "u" ) ; -$lb_u = CAPA_tex( "/*lb/*" , "lb" ) ; -$s_u = CAPA_tex( "/*s/*" , "s" ) ; -$min_u = CAPA_tex( "/*min/*" , "min" ) ; -$K_u = CAPA_tex( "/*K/*" , "K" ) ; -$degC_u = CAPA_tex( "/*^{\circ}\hspace*{-.015in}C/*" , "degC" ) ; -$degF_unit = CAPA_tex( "/*^{\circ}\hspace*{-.025in}F/*" , "degF" ) ; -$kmol_u = CAPA_tex( "/*kmol/*" , "kmol" ) ; -$Hz_u = CAPA_tex( "/*Hz/*" , "Hz" ) ; -$mPs_u = CAPA_tex( "/*m/s/*" , "m/s" ) ; -$cmPs_u = CAPA_tex( "/*cm/s/*" , "cm/s" ) ; -$kmPh_u = CAPA_tex( "/*km/h/*" , "km/h" ) ; -$miPh_u = CAPA_tex( "/*mi/h/*" , "mi/h" ) ; -$radPs_u = CAPA_tex( "/*rad/s/*" , "rad/s" ) ; -$Ps_u = CAPA_tex( "/*s^{-1}/*" , "s^-1" ) ; -$mPs2_u = CAPA_tex( "/*m/s^2/*" , "m/s2" ) ; -$cmPs2_u = CAPA_tex( "/*cm/s^2/*" , "cm/s2" ) ; -$radPs2_u = CAPA_tex( "/*rad/s^2/*" , "rad/s2" ) ; -$N_unit = CAPA_tex( "/*N/*" , "N" ) ; -$Pa_u = CAPA_tex( "/*Pa/*" , "Pa" ) ; -$atm_u = CAPA_tex( "/*atm/*" , "atm" ) ; -$NPm2_u = CAPA_tex( "/*N/m^2/*" , "N/m2" ) ; -$J_u = CAPA_tex( "/*J/*" , "J" ) ; -$hp_u = CAPA_tex( "/*hp/*" , "hp" ) ; -$eV_u = CAPA_tex( "/*eV/*" , "eV" ) ; -$cal_u = CAPA_tex( "/*cal/*" , "cal" ) ; -$kcal_u = CAPA_tex( "/*kcal/*" , "kcal" ) ; -$W_u = CAPA_tex( "/*W/*" , "W" ) ; -$kW_u = CAPA_tex( "/*kW/*" , "kW" ) ; -$Js_u = CAPA_tex( "/*J s/*" , "J s" ) ; -$C_u = CAPA_tex( "/*C/*" , "C" ) ; -$V_u = CAPA_tex( "/*V/*" , "V" ) ; -$ohm_u = CAPA_tex( "/*\Omega/*" , "ohm" ) ; -$F_u = CAPA_tex( "/*F/*" , "F" ) ; -$Wb_u = CAPA_tex( "/*Wb/*" , "Wb" ) ; -$H_u = CAPA_tex( "/*H/*" , "H" ) ; -$T_u = CAPA_tex( "/*T/*" , "T" ) ; -$N_u = CAPA_tex( "/*N/*" , "N" ) ; -$JPK_u = CAPA_tex( "/*J\over K/*" , "J/K" ) ; -$Nm2Pkg2_u = CAPA_tex( "/*Nm2\over kg^2/*" , "Nm^2/kg^2" ) ; -$kgPm3_u = CAPA_tex( "/*kg / m^3/*" , "kg/m3" ) ; -$gPm3_u = CAPA_tex( "/*g / m^3/*" , "g/m3" ) ; -$gPcm3_u = CAPA_tex( "/*g / cm^3/*" , "g/cm3" ) ; -$NPm_u = CAPA_tex( "/*N/m/*" , "N/m" ) ; -$kgm2_u = CAPA_tex( "/*kg\cdot m^2/*" , "kgm^2" ) ; -$deg_u = CAPA_tex( "/*^{\circ}/*" , "deg" ) ; -$rev_u = CAPA_tex( "/*rev/*" , "rev" ) ; -$rad_u = CAPA_tex( "/*rad/*" , "rad" ) ; -$G_u = CAPA_tex( "/*G/*" , "G" ) ; -$g_u = CAPA_tex( "/*g/*" , "g" ) ; -$h_u = CAPA_tex( "/*h/*" , "h" ) ; -$ton_u = CAPA_tex( "/*ton/*" , "ton" ) ; -$knots_u = CAPA_tex( "/*knots/*" , "knots" ) ; -$kgPs_u = CAPA_tex( "/*kg/s/*" , "kg/s" ) ; - -1; -################################################# -## Processing time = 5 secs ( 4.60 usr 0.37 sys = 4.97 cpu) -################################################# diff --git a/macros/contexts/contextAlternateDecimal.pl b/macros/contexts/contextAlternateDecimal.pl index 22750b63c1..951085dfa0 100644 --- a/macros/contexts/contextAlternateDecimal.pl +++ b/macros/contexts/contextAlternateDecimal.pl @@ -75,7 +75,7 @@ =head1 DESCRIPTION ); would allow students to enter decimals in either format, but all -numebrs would be displayed in standard form. +numbers would be displayed in standard form. =head2 LISTS IN ALTERNATE FORMAT @@ -92,7 +92,7 @@ =head2 LISTS IN ALTERNATE FORMAT this makes things like C<3,2,1> tricky, because it is not clear if this is 3.2 followed by 1, or 3.2 times .1, or the list of 3, 2, and 1. To help make this unambiguous, numbers that use a comma as decimal -inidcator must have a digit on both sides of the comma. So one tenth +indicator must have a digit on both sides of the comma. So one tenth would have to be entered as C<0,1> not just C<,1> (but you can still enter C<.1>. Similarly, You must enter C<3,0> or just C<3> rather than C<3,>, even though C<3.> is acceptable. diff --git a/macros/contexts/contextAlternateIntervals.pl b/macros/contexts/contextAlternateIntervals.pl index c4a1147290..f32d5d466f 100644 --- a/macros/contexts/contextAlternateIntervals.pl +++ b/macros/contexts/contextAlternateIntervals.pl @@ -244,7 +244,7 @@ sub Open { # # We need to modify the test for formInterval to NOT check the number # of entries so that better error messages are produced, and to handle -# multiple close delimiters. These are both in teh "operand" branch, +# multiple close delimiters. These are both in the "operand" branch, # so do the original for all the choices, and copy that branch here, # with our modifications. # diff --git a/macros/contexts/contextArbitraryString.pl b/macros/contexts/contextArbitraryString.pl index 45240c98a1..5c8a2b94ad 100644 --- a/macros/contexts/contextArbitraryString.pl +++ b/macros/contexts/contextArbitraryString.pl @@ -33,7 +33,7 @@ =head1 DESCRIPTION case are significant), then you don't have to use a custom checker. But if you want, for example, to collapse multiple white-space, or trim leading and trailing blanks, or treat upper- and lower-case -letters as quivalent, then you will need to provide your own +letters as equivalent, then you will need to provide your own checker that does that. This context handles multi-line answers properly. If your answers diff --git a/macros/contexts/contextBaseN.pl b/macros/contexts/contextBaseN.pl index de631c3b41..fd7ab81bba 100644 --- a/macros/contexts/contextBaseN.pl +++ b/macros/contexts/contextBaseN.pl @@ -11,7 +11,7 @@ =head1 DESCRIPTION and shown in the chosen base. The original purpose for this is simple conversion and operations in another base, however -it is not limited to this. In addition, basic integer arithemetic (+,-,*,/,%,^) are available for these numbers. +it is not limited to this. In addition, basic integer arithmetic (+,-,*,/,%,^) are available for these numbers. Division is defined in an integer sense. To use a non-decimal base MathObject, first load the contextBaseN.pl file: diff --git a/macros/contexts/contextBoolean.pl b/macros/contexts/contextBoolean.pl index e227ea587e..ad765183d2 100644 --- a/macros/contexts/contextBoolean.pl +++ b/macros/contexts/contextBoolean.pl @@ -83,7 +83,7 @@ =head1 OPERATOR PRECEDENCE $a = Compute("T or T and F"); # $a == F -The C setting priortizes C < C < C < C. +The C setting prioritizes C < C < C < C. Context()->setPrecedence('oxan'); $b = Compute("T or T and F"); # $b == T @@ -417,14 +417,14 @@ sub perl { return "($result ? context::Boolean->T : context::Boolean->F)"; } -# remove once UOP::string passses 'same' as second argument +# remove once UOP::string passes 'same' as second argument sub string { my ($self, $precedence, $showparens, $position, $outerRight) = @_; $showparens = "same" if !($position // '') && !($showparens // ''); return $self->SUPER::string($precedence, $showparens, $position, $outerRight); } -# remove once UOP::TeX passses 'same' as second argument +# remove once UOP::TeX passes 'same' as second argument sub TeX { my ($self, $precedence, $showparens, $position, $outerRight) = @_; $showparens = "same" if !($position // '') && !($showparens // ''); diff --git a/macros/contexts/contextComplexJ.pl b/macros/contexts/contextComplexJ.pl index b2e0d20cda..2cc91e1bc0 100644 --- a/macros/contexts/contextComplexJ.pl +++ b/macros/contexts/contextComplexJ.pl @@ -62,7 +62,7 @@ =head1 DESCRIPTION ); would allow students to enter complex numbers in either format, but -all numebrs would be displayed in standard form. +all numbers would be displayed in standard form. =head2 SETTING THE ALTERNATE FORM AS THE DEFAULT diff --git a/macros/contexts/contextCongruence.pl b/macros/contexts/contextCongruence.pl index 71bf8047e6..d69287c891 100644 --- a/macros/contexts/contextCongruence.pl +++ b/macros/contexts/contextCongruence.pl @@ -16,7 +16,7 @@ =head1 DESCRIPTION requires the general solution, and C, which requires all solutions to be entered. -Congruences must be created with three paramters (a, b, m) from ax ≡ b (mod m). +Congruences must be created with three parameters (a, b, m) from ax ≡ b (mod m). loadMacros("contextCongruence.pl"); @@ -160,7 +160,7 @@ sub compare { } elsif ($requireAllSolutions) { return $allSolutions->compare($r); } else { - # check both all solutons and general solution + # check both all solutions and general solution return 0 if $allSolutions->compare($r) == 0; return $generalSolution->compare($r); } diff --git a/macros/contexts/contextCurrency.pl b/macros/contexts/contextCurrency.pl index c8580737cb..c83b473876 100644 --- a/macros/contexts/contextCurrency.pl +++ b/macros/contexts/contextCurrency.pl @@ -32,7 +32,7 @@ =head1 DESCRIPTION Context("Currency")->currency->addSymbol("dollars","dollar"); would accept '$12,345.67' or '12.50 dollars' or '1 dollar' as -acceptable values. Note that if the symbol cantains any +acceptable values. Note that if the symbol contains any alphabetic characters, it is expected to come at the end of the number (as in the examples above) and if the symbol has only non-alphabetic characters, it comes before it. You can change diff --git a/macros/contexts/contextFiniteSolutionSets.pl b/macros/contexts/contextFiniteSolutionSets.pl index bf98737afd..e5452a9ac0 100644 --- a/macros/contexts/contextFiniteSolutionSets.pl +++ b/macros/contexts/contextFiniteSolutionSets.pl @@ -283,6 +283,7 @@ sub _contextFiniteSolutionSets_init { . join(',', map { $_->TeX } (@correctanswers)) . "\\right\\}\\)"); } + $score = 0 if $score < 0; return ($score, @errors); }; } diff --git a/macros/contexts/contextForm.pl b/macros/contexts/contextForm.pl index 6d26f3cdf3..1c472cfa2e 100644 --- a/macros/contexts/contextForm.pl +++ b/macros/contexts/contextForm.pl @@ -8,10 +8,10 @@ =head1 DESCRIPTION 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 +commutative and associative addition and multiplication, so it would be OK to answer with "(2+x)(x+1)". -But this context initally only uses bizarro with multiplication and division. +But this context initially 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)" diff --git a/macros/contexts/contextFraction.pl b/macros/contexts/contextFraction.pl index 76efacc700..98f1f5b279 100644 --- a/macros/contexts/contextFraction.pl +++ b/macros/contexts/contextFraction.pl @@ -149,7 +149,7 @@ =head1 DESCRIPTION C is set), so C would produce the fraction C<2/3>. To leave fractions unreduced, set C 0 >>>. The C and -C contexts have C +C contexts have C set, so C is unset automatically for students, but not for correct answers, so C would still produce C<1/2>, even though C<2/4> would not be allowed in a student answer. @@ -207,7 +207,7 @@ =head1 DESCRIPTION =item S>> -Thie determines the tolerance to use when comparing a fraction to a +This determines the tolerance to use when comparing a fraction to a real number. The fraction will be converted to a real, and then this is used as the tolerance in a relative-tolerance comparison of the two reals. The default is 1E-10, meaning the decimal must match to @@ -1012,7 +1012,7 @@ sub power { } return $self->inherit($other)->make($x, $y) unless $x eq 'nan' || $y eq 'nan'; Value::Error("Can't raise a negative number to a non-integer power") if $a * $b < 0; - Value::Error("Result of exponention is not a number"); + Value::Error("Result of exponentiation is not a number"); } sub compare { diff --git a/macros/contexts/contextInequalities.pl b/macros/contexts/contextInequalities.pl index 226af7e8cd..14d4a19ae4 100644 --- a/macros/contexts/contextInequalities.pl +++ b/macros/contexts/contextInequalities.pl @@ -74,7 +74,7 @@ =head1 DESCRIPTION $I0 = Compute("1 < x <= 2"); # the inequality 1 < x <= 2 $I1 = Interval($I0); # the interval (1,2] -Note that ineqaulities and inervals can be compared and combined +Note that inequalities and intervals can be compared and combined regardless of the format, so C<$I0 == $I1> is true in either example above. diff --git a/macros/contexts/contextInteger.pl b/macros/contexts/contextInteger.pl index b9fbf2fc3c..bab7c7ef07 100644 --- a/macros/contexts/contextInteger.pl +++ b/macros/contexts/contextInteger.pl @@ -74,7 +74,7 @@ =head2 isPrime =cut -loadMacros('MathObjects.pl'); +loadMacros('PGbasicmacros.pl', 'MathObjects.pl'); sub _contextInteger_init { context::Integer::Init() } @@ -124,7 +124,7 @@ sub Init { sub _divisor { my $power = abs(shift); my $a = abs(shift); - $self->Error("Cannot perform divisor function on Zero") if $a == 0; + Value::Error('Cannot perform divisor function on Zero') if $a == 0; $result = 1; $sqrt_a = int(sqrt($a)); for (my $i = 2; $i < $sqrt_a; $i++) { @@ -146,9 +146,9 @@ sub _divisor { sub _getPrimesInRange { my $index = shift; my $end = shift; - $self->Error("Start of range must be a positive number.") if $index < 0; - $self->Error("End of range must be greater than or equal to 2") if $end < 2; - $self->Error("Start or range must be before end of range") if $index > $end; + Value::Error('Start of range must be a positive number.') if $index < 0; + Value::Error('End of range must be greater than or equal to 2') if $end < 2; + Value::Error('Start of range must be before end of range') if $index > $end; @primes = (); # consider switching to set upper limit and static array of primes @@ -156,7 +156,7 @@ sub _getPrimesInRange { push(@primes, 2) if $index <= 2; # ensure index is odd $index++ if $index % 2 == 0; - while ($index < $end) { + while ($index <= $end) { push(@primes, $index) if context::Integer::Function::Numeric::isPrime($index); $index += 2; } @@ -172,8 +172,8 @@ package context::Integer::Function::Numeric; # sub primeFactorization { my $a = abs(shift); - $self->Error("Cannot factor Zero into primes.") if $a == 0; - $self->Error("Cannot factor One into primes.") if $a == 1; + Value::Error('Cannot factor Zero into primes.') if $a == 0; + Value::Error('Cannot factor One into primes.') if $a == 1; my %factors; my $n = $a; @@ -200,7 +200,7 @@ sub primeFactorization { # sub phi { my $a = abs(shift); - $self->Error("Cannot phi on Zero.") if $a == 0; + Value::Error('Cannot phi on Zero.') if $a == 0; $result = $a; $n = $a; for (my $i = 2; ($i**2) <= $n; $i++) { @@ -235,9 +235,8 @@ sub isPrime { sub randomPrime { my ($start, $end) = @_; my @primes = context::Integer::_getPrimesInRange($start, $end); - $self->Error("Could not find any prime numbers in range.") if $#primes == 0; - my $primeIndex = $main::PG_random_generator->random(0, ($#primes - 1), 1); - return $primes[$primeIndex]; + Value::Error('Could not find any prime numbers in range.') unless @primes; + return main::list_random(@primes); } package context::Integer::Function::Numeric2; diff --git a/macros/contexts/contextLimitedPolynomial.pl b/macros/contexts/contextLimitedPolynomial.pl index b3baf8ef60..07fa9f87a8 100644 --- a/macros/contexts/contextLimitedPolynomial.pl +++ b/macros/contexts/contextLimitedPolynomial.pl @@ -236,7 +236,7 @@ sub checkPolynomial { sub checkStrict { my $self = shift; - $self->Error("You can only use '%s' between coefficents and variables in a simplified polynomial", $self->{bop}); + $self->Error("You can only use '%s' between coefficients and variables in a simplified polynomial", $self->{bop}); } ############################################## diff --git a/macros/contexts/contextOrdering.pl b/macros/contexts/contextOrdering.pl index 34b7d9e431..5e88aaee3e 100644 --- a/macros/contexts/contextOrdering.pl +++ b/macros/contexts/contextOrdering.pl @@ -233,11 +233,11 @@ sub TeX { # # This is the Value object used to implement the list That represents # one ordering operation. It is simply a normal Value::List with the -# operator as the first entry and the two operands as the remaing -# entries in the list. The new() method is overriden to make binary +# operator as the first entry and the two operands as the remaining +# entries in the list. The new() method is overridden to make binary # trees of equal operators into flat sorted lists. We override the # List string and TeX methods so that they print correctly as binary -# operators. The cmp_equal method is overriden to make sure the that +# operators. The cmp_equal method is overridden to make sure the that # the lists are treated as a unit during answer checking. There is # also a routine for adding letters to the object's context. # diff --git a/macros/contexts/contextPercent.pl b/macros/contexts/contextPercent.pl index 48c43b687e..4f81450fea 100644 --- a/macros/contexts/contextPercent.pl +++ b/macros/contexts/contextPercent.pl @@ -49,7 +49,7 @@ =head1 DESCRIPTION You can enforce more restrictive rules using the C or C contexts. The first of these allows -operations within the number that preceeds the percent sign, but does +operations within the number that precedes the percent sign, but does not allow you to combine percentages with real numbers or other percentages. In this way, C<(5+5)%> is legal, but C<5% + 5%> is not. @@ -119,7 +119,7 @@ =head1 DESCRIPTION the number given by the C flag. If set to 0 (the default for the C, C, and C contexts), any number of decimals are allowed (but the C -determines what values are meaningfull), while if set to 1 (the +determines what values are meaningful), while if set to 1 (the default for the C context), students are not allowed to enter more than the required number of decimals and receive a warning if they do. diff --git a/macros/contexts/contextPermutation.pl b/macros/contexts/contextPermutation.pl index b55c75a253..dc8a8d2a46 100644 --- a/macros/contexts/contextPermutation.pl +++ b/macros/contexts/contextPermutation.pl @@ -19,7 +19,7 @@ =head1 SYNOPSIS $P4 = Compute("(1 2 3)^2"); # square a cycle $P5 = Compute("((1 2)(3 4))^2"); # square a permutation - $I = Comptue("(1 2 3)^-1"); # inverse + $I = Compute("(1 2 3)^-1"); # inverse $L = Compute("(1 2),(1 3 2)"); # list of permutations @@ -540,7 +540,7 @@ sub _check { # # Evaluate by forming a list if this is acting as a comma, -# othewise take a product (Value object will take care of things). +# otherwise take a product (Value object will take care of things). # sub _eval { my $self = shift; diff --git a/macros/contexts/contextPermutationUBC.pl b/macros/contexts/contextPermutationUBC.pl index bf9ab19bdf..bc17af6dd5 100644 --- a/macros/contexts/contextPermutationUBC.pl +++ b/macros/contexts/contextPermutationUBC.pl @@ -11,7 +11,7 @@ =head1 DESCRIPTION parentheses. Cycles are multiplied by juxtaposition. A permutation can be multiplied on the left by a number in order to obtain the result of that number under the action of the permutation. -Exponentiation is alos allowed (as described below). +Exponentiation is also allowed (as described below). There are three contexts included here: C, which allows permutations in any form, C, which @@ -35,7 +35,7 @@ =head1 USAGE $P4 = Compute("(1 2 3)^2"); # square a cycle $P5 = Compute("((1 2)(3 4))^2"); # square a permutation - $I = Comptue("(1 2 3)^-1"); # inverse + $I = Compute("(1 2 3)^-1"); # inverse $L = Compute("(1 2),(1 3 2)"); # list of permutations @@ -603,7 +603,7 @@ sub _check { # # Evaluate by forming a list if this is acting as a comma, -# othewise take a product (Value object will take care of things). +# otherwise take a product (Value object will take care of things). # sub _eval { my $self = shift; diff --git a/macros/contexts/contextPiecewiseFunction.pl b/macros/contexts/contextPiecewiseFunction.pl index 08e6110b2a..6113f842f9 100644 --- a/macros/contexts/contextPiecewiseFunction.pl +++ b/macros/contexts/contextPiecewiseFunction.pl @@ -30,7 +30,7 @@ =head1 DESCRIPTION of the function (the formula to use of the input is not in any of the given intervals). -Note that you can use Inveral, Set, or Union objects in place of +Note that you can use Interval, Set, or Union objects in place of the intervals in the specification of a piecewise function. The PiecewiseFunction object TeXifies using a LaTeX "cases" @@ -345,7 +345,7 @@ sub _eval { # This implements the "in" operator as in inequality. # We inherit all the inequality methods, and simply # need to handle the string and TeX output. The -# underlying type is still an Inerval. +# underlying type is still an Interval. # package PiecewiseFunction::Interval; our @ISA = ("Inequalities::Interval"); diff --git a/macros/contexts/contextPolynomialFactors.pl b/macros/contexts/contextPolynomialFactors.pl index 346d0ea687..efec810384 100644 --- a/macros/contexts/contextPolynomialFactors.pl +++ b/macros/contexts/contextPolynomialFactors.pl @@ -130,7 +130,7 @@ sub checkFactors { sub checkStrict { my $self = shift; - $self->Error("You can only use '%s' between coefficents and variables or between factors", $self->{bop}); + $self->Error("You can only use '%s' between coefficients and variables or between factors", $self->{bop}); } ############################################## @@ -223,7 +223,7 @@ package PolynomialFactors::Formula; ############################################## package PolynomialFactors; -our @ISA = ('LimitedPolynomal'); +our @ISA = ('LimitedPolynomial'); sub markFactor { my $self = shift; diff --git a/macros/contexts/contextReaction.pl b/macros/contexts/contextReaction.pl index 057783ad5e..ecf71698c1 100644 --- a/macros/contexts/contextReaction.pl +++ b/macros/contexts/contextReaction.pl @@ -25,10 +25,15 @@ =head1 DESCRIPTION $R = Formula("4P + 5O_2 --> 2P_2O_5"); -Ions can be specified using C<^> to produce superscripts, as in C or -C. Note that the charge may be listed with prefix notation (+2) -or postfix notation (2+). A sign by itself is assumed to have number -1, so that C is equivalent to C. +Ions can be specified using C<^> to produce superscripts, as in +C or C. Note that the charge may be listed with prefix +notation (+2) or postfix notation (2+). By default, TeX will display +only the sign of the charge of singly charged ions, e.g., C and +C. However, the context flag C<< showUnity=>1 >> can be used to +direct TeX to use an alternative notation for singly charged ions, by +which the numerical magnitude (1) is displayed along with the sign of +the charge, e.g., C and C. The default value of +C<> is zero. States can be appended to compounds, as in C. So you can make reactions like the following: @@ -44,9 +49,9 @@ =head1 DESCRIPTION These can be used in reactions as with any other compound. -Reactions know how to create their own TeX versions (via C<< $R->TeX >>), and -know how to check student answers (via C<< $R->cmp >>), just like any other -MathObject. +Reactions know how to create their own TeX versions (via C<< $R->TeX >>), +and know how to check student answers (via C<< $R->cmp >>), just like +any other MathObject. The Reaction Context also allows you to create parts of reactions. E.g., you could create @@ -108,6 +113,48 @@ =head1 DESCRIPTION to allow a state of C<(x)> for a compound. +By default, the Reaction context checks the student answer against the +correct answer in the form the correct answer is given (molecular or +condensed structural form). If you want to allow either form, then +you can set the flag C to C<1>, in which case, +the student's answer is compared first to the original correct answer +and then to its molecular form. For example, if the correct answer is +C<(NH_4)_3PO_4>, then a student answer of C would also be +considered correct when this flag is set to C<1>. + +You can convert a compound (or every term of a complete reaction) to +molecular form using the C method of any Reaction +object. For example + + $R = Compute("CH_3CH_2CH_3")->molecularForm; + +would make C<$R> be the equivalent of C. + +Note that student answers aren't reduced to molecular form when +C is true. If you want to allow students to +enter compounds in any form, you can set the C flag +to C<1>, in which case both the correct and student answers are +formatted in molecular form internally before they are compared. + +The molecular form will have the elements in the order that they first +appeared in the compound, and the student must use the same order for +their answer to be considered correct. If you want to allow the +student to enter the elements in any order, you can set the +C flag to C<0> which will cause the molecular form +to be in alphabetical order. That way, if the student has the correct +number of each element, in any order, the two answers will match. + +The Reaction context can perform an automatic reduction that combines +adjacent elements that are the same. This is controlled by the +C flag, which is C<1> by default. For +example, + + $R1 = Compute("COOH"); + $R2 = Compute("CO_2H"); + +would equal each other when the flag is set, even when +C is not set. + =cut ###################################################################### @@ -142,19 +189,16 @@ sub Init { $context->functions->clear(); $context->strings->clear(); $context->constants->clear(); - $context->lists->clear(); - $context->lists->add( + $context->lists->are( 'List' => { class => 'context::Reaction::List::List', open => '', close => '', separator => ' + ' }, 'Complex' => { class => 'context::Reaction::List::Complex', open => '[', close => ']', separator => '' }, ); - $context->parens->clear(); - $context->parens->add( + $context->parens->are( '(' => { close => ')', type => 'List', formList => 1, removable => 1 }, '{' => { close => '}', type => 'List', removable => 1 }, '[' => { close => ']', type => 'Complex' }, ); - $context->operators->clear(); - $context->operators->set( + $context->operators->are( '-->' => { precedence => 1, associativity => 'left', @@ -259,8 +303,16 @@ sub Init { ); $context->variables->add(map { $_ => $STATE } ('(aq)', '(s)', '(l)', '(g)', '(ppt)')); $context->reductions->clear(); - $context->flags->set(studentsMustUseStates => 1); - $context->flags->set(reduceConstants => 0); + $context->flags->set( + showUnity => 0, + studentsMustUseStates => 1, + reduceConstants => 1, + combineAdjacentElements => 1, + acceptMolecularForm => 0, + compareMolecular => 0, + keepElementOrder => 1, + mathQuillOpts => { includeIonButtons => 1 } + ); $context->{parser}{Number} = 'context::Reaction::Number'; $context->{parser}{Variable} = 'context::Reaction::Variable'; $context->{parser}{Formula} = 'context::Reaction'; @@ -277,14 +329,14 @@ sub Init { # Handle postfix - and + in superscripts # sub Op { - my $self = shift; - my $name = shift; - my $ref = $self->{ref} = shift; + my $self = shift; + my $name = shift; + my $ref = $self->{ref} = shift; + my $context = $self->{context}; + my $op; + ($name, $op) = $context->operators->resolve($name); + ($name, $op) = $context->operators->resolve($op->{space}) if $self->{space} && defined($op->{space}); if ($self->state eq 'operand') { - my $context = $self->{context}; - my $op; - ($name, $op) = $context->operators->resolve($name); - ($name, $op) = $context->operators->resolve($op->{space}) if $self->{space} && defined($op->{space}); if ($op->{type} eq 'both' && $context->{operators}{"p$name"} && $self->top->{value}->class eq 'Number' @@ -292,23 +344,32 @@ sub Op { && $self->prev->{name} eq '^') { ($name, $op) = $context->operators->resolve("p$name"); - $self->pushOperand($self->Item('UOP')->new($self, $name, $self->pop->{value}, $ref)); + $self->pushCharge($name, $self->pop->{value}); return; } - } elsif ($self->state eq 'operator' && $self->top->{name} =~ m/^u/) { - $self->SimpleCharge; } $self->SUPER::Op($name, $ref); } # -# Handle superscripts of just + or - +# Handle superscripts of just + or - or postscript + or - # sub Close { my $self = shift; my $type = shift; my $ref = $self->{ref} = shift; $self->SimpleCharge if $self->state eq 'operator' && $self->top->{name} =~ m/^u/; + my $name = $self->top->{name}; + my $context = $self->{context}; + if ($self->state eq 'operator' + && $context->{operators}{"p$name"} + && $self->prev->{type} eq 'operand' + && $self->top(-2)->{type} eq 'open') + { + $self->pop; + ($name) = $context->operators->resolve("p$name"); + $self->pushCharge($name, $self->pop->{value}); + } $self->SUPER::Close($type, $ref); } @@ -322,11 +383,18 @@ sub pushOperand { $self->push({ type => 'operand', ref => $self->{ref}, value => $value }); } +sub pushCharge { + my $self = shift; + my $op = shift; + my $value = shift; + $self->pushOperand($self->Item('UOP')->new($self, $op, $value, $self->{ref})); +} + sub SimpleCharge { my $self = shift; my $top = $self->pop; my $one = $self->Item('Number')->new($self, 1, $self->{ref}); - $self->pushOperand($self->Item('UOP')->new($self, $top->{name}, $one, $self->{ref})); + $self->pushCharge($top->{name}, $one); } # @@ -341,11 +409,20 @@ sub compare { } # -# Don't allow evaluation +# Don't allow evaluation except for numbers # sub eval { my $self = shift; - $self->Error("Can't evaluate " . $self->TYPE); + return $self->Package('Real')->new($self->{tree}{value}) if $self->{tree}->class eq 'Number'; + $self->Error("Can't evaluate " . $self->{tree}->TYPE); +} + +# +# Get the molecular form for everything in the reaction +# +sub molecularForm { + my $self = shift; + return $self->new($self->{tree}->molecularForm); } # @@ -383,12 +460,71 @@ sub typeMatch { return 1; } +###################################################################### +# +# Common functions to multiple classes +# +package context::Reaction::common; + +# +# Shorthand for creating parser items +# +sub ITEM { + my $self = shift; + return $self->Item(shift)->new($self->{equation}, @_); +} + +# +# Convert an item to a compound +# +sub COMPOUND { + my $self = shift; + return $self->ITEM('BOP', ' ', $self->ITEM('Number', 1), $self->copy); +} + +# +# Make a charge item from a number +# +sub CHARGE { + my ($self, $n) = @_; + return $self->ITEM('UOP', $n < 0 ? 'u-' : 'u+', $self->ITEM('Number', CORE::abs($n))); +} + +# +# Add a term into an item's data +# +sub combineData { + my ($self, $other) = @_; + return unless $other->{order}; + my ($order, $elements) = ($self->{order}, $self->{elements}); + for my $element (@{ $other->{order} }) { + if (!$elements->{$element}) { + push(@$order, $element); + $elements->{$element} = 0; + } + $elements->{$element} += $other->{elements}{$element}; + } + $self->{charge} += $other->{charge}; + $self->{factor} *= $other->{factor}; +} + +# +# Molecular form is same as original +# +sub molecularForm { shift->copy } + +# +# Default is not a checmicla or sum +# +sub isChemical {0} +sub isSum {0} + ###################################################################### # # The replacement for the Parser:Number class # package context::Reaction::Number; -our @ISA = ('Parser::Number'); +our @ISA = ('Parser::Number', 'context::Reaction::common'); # # Equivalent is equal @@ -400,8 +536,6 @@ sub equivalent { return $self->eval == $other->eval; } -sub isChemical {0} - sub class {'Number'} sub TYPE {'a Number'} @@ -410,7 +544,21 @@ sub equivalent { # The replacement for Parser::Variable. We hold the elements here. # package context::Reaction::Variable; -our @ISA = ('Parser::Variable'); +our @ISA = ('Parser::Variable', 'context::Reaction::common'); + +# +# Save the element data +# +sub new { + my $self = shift->SUPER::new(@_); + if ($self->type ne 'State') { + $self->{order} = [ $self->{name} ]; + $self->{elements} = { $self->{name} => 1 }; + $self->{charge} = 0; + $self->{factor} = 1; + } + return $self; +} # # Two elements are equivalent if their names are equal @@ -452,7 +600,7 @@ sub TYPE { # are subclasses of this). # package context::Reaction::BOP; -our @ISA = ('Parser::BOP'); +our @ISA = ('Parser::BOP', 'context::Reaction::common'); # # Binary operators produce chemicals (unless overridden, as in arrow) @@ -461,6 +609,17 @@ package context::Reaction::BOP; sub eval { context::Reaction::eval(@_) } +# +# Form molecular form by combining molecular forms of the operands +# +sub molecularForm { + my $self = shift; + $self = bless {%$self}, ref($self); + $self->{lop} = $self->{lop}->molecularForm; + $self->{rop} = $self->{rop}->molecularForm; + return $self; +} + # # Two nodes are equivalent if their operands are equivalent # and they have the same operator @@ -468,13 +627,19 @@ package context::Reaction::BOP; sub equivalent { my $self = shift; my $other = shift; - # return $other->equivalent($self) - # if $other->class eq 'BOP' && $other->{rop}->type eq 'State' && $self->{rop}->type ne 'State'; - return 0 unless $other->class eq 'BOP'; - return 0 unless $self->{bop} eq $other->{bop}; + return 0 unless $other->class eq 'BOP' && $self->{bop} eq $other->{bop}; return $self->{lop}->equivalent($other->{lop}) && $self->{rop}->equivalent($other->{rop}); } +# +# Check for equivalence using string representations of compounds +# +sub equivalentTo { + my ($self, $other, $states) = @_; + $other = $other->string; + return $self->string eq $other || (!$states && $self->{hasState} && $self->{lop}->string eq $other); +} + ###################################################################### # # Implements the --> operator @@ -492,10 +657,11 @@ package context::Reaction::BOP::arrow; # sub _check { my $self = shift; - $self->Error("The left-hand side of '-->' must be a (sum of) reactants, not %s", $self->{lop}->TYPE) - unless $self->{lop}->isChemical; - $self->Error("The right-hand side of '-->' must be a (sum of) products, not %s", $self->{rop}->TYPE) - unless $self->{rop}->isChemical; + my ($lop, $rop) = ($self->{lop}, $self->{rop}); + $self->Error("The left-hand side of '-->' must be a (sum of) reactants, not %s", $lop->TYPE) + unless $lop->isChemical || $lop->isSum; + $self->Error("The right-hand side of '-->' must be a (sum of) products, not %s", $rop->TYPE) + unless $rop->isChemical || $rop->isSum; $self->{type} = $REACTION->{type}; } @@ -514,8 +680,9 @@ package context::Reaction::BOP::add; # sub _check { my $self = shift; - $self->Error("Can't add %s and %s", $self->{lop}->TYPE, $self->{rop}->TYPE) - unless $self->{lop}->isChemical && $self->{rop}->isChemical; + my ($lop, $rop) = ($self->{lop}, $self->{rop}); + $self->Error("Can't combine %s and %s", $lop->TYPE, $rop->TYPE) + unless ($lop->isChemical || $lop->isSum) && ($rop->isChemical || $rop->isSum); $self->SUPER::_check(@_); } @@ -534,6 +701,11 @@ sub equivalent { sub TYPE {'a sum of Compounds'} +# +# It is a sum +# +sub isSum {1} + ###################################################################### # # Implements concatenation, which produces compounds or integer @@ -548,55 +720,186 @@ package context::Reaction::BOP::multiply; sub _check { my $self = shift; my ($lop, $rop) = ($self->{lop}, $self->{rop}); - $self->Error("Can't combine %s and %s", $lop->TYPE, $rop->TYPE) + $self->Error("Can't use a numeric prefix with %s", $rop->TYPE) unless ($lop->class eq 'Number' || $lop->isChemical) && $rop->isChemical; $self->Error("Compound already has a state") if $lop->{hasState} && $rop->type eq 'State'; $self->Error("Can't combine %s with %s", $lop->TYPE, $rop->TYPE) if $lop->{hasState} && $rop->isChemical; $self->Error("Can't combine %s with %s", $lop->TYPE, $rop->TYPE) if $rop->{hasState} && $lop->isChemical; $self->Error("Can't combine %s with %s", $lop->{name}, $rop->TYPE) if $lop->type eq 'Constant'; $self->Error("Can't combine %s with %s", $lop->TYPE, $rop->{name}) if $rop->type eq 'Constant'; + $self->Error("Can't combine %s with %s", $lop->TYPE, $rop->TYPE) + if $lop->class ne 'Number' && $rop->{hasNumber}; $self->{type} = $COMPOUND->{type}; - if ($self->{lop}{hasNumber}) { - my $n = $self->{lop}{lop}; - $self->{lop}{lop} = $self->{lop}{rop}; - $self->{lop}{rop} = $self->{rop}; - $self->{lop}{hasState} = 1 if $self->{rop}->type eq 'State'; - delete $self->{lop}{hasNumber}; - $self->{rop} = $self->{lop}; - $self->{lop} = $n; + $self->getCompound; + $self->{hasState} = 1 if $rop->type eq 'State'; + $self->{hasNumber} = 1 if $lop->type eq 'Number' || $lop->{hasNumber}; + + if ($lop->{hasNumber} && $rop->type ne 'State') { + my $n = $lop->{lop}; + $lop->{lop} = $lop->{rop}; + $lop->{rop} = $rop; + delete $lop->{hasNumber}; + $rop = $self->{rop} = $lop; + $lop = $self->{lop} = $n; + } + + $self->combineAdjacentNumbers if $self->context->flag('reduceConstants'); + $self->combineAdjacentElements if $self->context->flag('combineAdjacentElements'); +} + +# +# Get the order/element/charge data for a compound +# +sub getCompound { + my $self = shift; + my ($lop, $rop) = ($self->{lop}, $self->{rop}); + $self->{order} = []; + $self->{elements} = {}; + $self->{charge} = 0; + $self->{factor} = $lop->type eq 'Number' ? $lop->eval : 1; + $self->combineData($lop); + $self->combineData($rop); +} + +# +# Combine adjacent numbers (e.g., 2(3CO)) +# +sub combineAdjacentNumbers { + my $self = shift; + my ($lop, $rop) = ($self->{lop}, $self->{rop}); + if ($rop->{hasNumber} && $lop->class eq 'Number') { + if ($rop->{hasState}) { + $lop->{value} *= $rop->{lop}{lop}{value}; + $rop->{lop} = $rop->{lop}{rop}; + } else { + $lop->{value} *= $rop->{lop}{value}; + $rop = $self->{rop} = $rop->{rop}; + } + } +} + +# +# Combine adjacent elements if they are the same +# +sub combineAdjacentElements { + my $self = shift; + my ($lop, $rop) = ($self->{lop}, $self->{rop}); + return unless $lop->{order} && $rop->{order} && scalar(@{ $rop->{order} }) == 1; + my $name = $rop->{order}[0]; + if ($lop->type eq 'Compound') { + $self->combineCompound($lop, $rop); + } else { + $self->combineSimple($lop, $rop); } - $self->{hasState} = 1 if $self->{rop}->type eq 'State'; - $self->{hasNumber} = 1 if $self->{lop}->class eq 'Number'; } # -# Remove ground state, if needed +# Combine elements when the left operand is a compound +# +sub combineCompound { + my ($self, $lop, $rop) = @_; + my $last = $lop->{rop}; + my $name = $rop->{order}[0]; + return unless $last->{order} && scalar(@{ $last->{order} }) == 1 && $last->{order}[0] eq $name; + $self->{lop} = $lop->{lop}; + my $n = $self->ITEM('Number', $last->{elements}{$name} + $rop->{elements}{$name}); + $self->{rop} = $self->ITEM('BOP', '_', $self->ITEM('Variable', $name), $n); + my $charge = $last->{charge} + $rop->{charge}; + $self->{rop} = $self->ITEM('BOP', '^', $self->{rop}, $self->CHARGE($charge)) if $charge; +} + +# +# Combine elements when the left is not a compound +# +sub combineSimple { + my ($self, $lop, $rop) = @_; + my $name = $rop->{order}[0]; + return unless scalar(@{ $lop->{order} }) == 1 && $lop->{order}[0] eq $name; + my $n = $self->ITEM('Number', $lop->{elements}{$name} + $rop->{elements}{$name}); + my $element = $self->ITEM('Variable', $name); + my $charge = $lop->{charge} + $rop->{charge}; + if ($charge) { + $self->mutate('^', $self->ITEM('BOP', '_', $element, $n), $self->CHARGE($charge)); + } else { + $self->mutate('_', $element, $n); + } +} + +# +# Mutate the BOP into a different one +# +sub mutate { + my $self = shift; + my $other = $self->ITEM('BOP', @_); + delete $self->{$_} for (keys %$self); + $self->{$_} = $other->{$_} for (keys %$other); + bless $self, ref($other); +} + +# +# Create the molecular form for a compound +# +sub molecularForm { + my $self = shift; + my $elements = $self->{elements}; + my $state = $self->{hasState} ? $self->{rop} : undef; + my $number = $self->{factor} > 1 ? $self->ITEM('Number', $self->{factor}) : undef; + my @order = @{ $self->{order} }; + @order = main::lex_sort(@order) if !$self->context->flag('keepElementOrder'); + my $compound; + for (@order) { + my $term = $self->ITEM('Variable', $_); + $term = $self->ITEM('BOP', '_', $term, $self->ITEM('Number', $elements->{$_})) if $elements->{$_} > 1; + $compound = $compound ? $self->ITEM('BOP', ' ', $compound, $term) : $term; + } + $compound = $self->ITEM('BOP', '^', $compound, $self->CHARGE($self->{charge})) if $self->{charge}; + $compound = $self->ITEM('BOP', ' ', $number, $compound) if $number; + $compound = $self->ITEM('BOP', ' ', $compound, $state->copy) if $state; + return $compound; +} + +# +# Check for equivalence of two compounds +# removing states if needed and student answers can be without them # sub equivalent { my $self = shift; my $other = shift; if ($other->class eq 'List') { my $parens = $self->context->parens->get('('); - my $list = $self->Item('List')->new($self->{equation}, [$self], 1, $parens); + my $list = $self->ITEM('List', $self->{equation}, [$self], 1, $parens); return $list->equivalent($other); } - my $states = $self->context->flags->get('studentsMustUseStates'); - my $equiv = $self->SUPER::equivalent($other); - return ($equiv || !$self->{hasState} || $states ? $equiv : $self->{lop}->equivalent($other)); + return 0 unless $other->isChemical; + $other = $other->COMPOUND unless $other->type eq 'Compound'; + my $states = $self->context->flag('studentsMustUseStates'); + my $molecular = $self->context->flag('compareMolecular'); + my $both = $self->context->flag('acceptMolecularForm'); + return $self->molecularForm->equivalentTo($other->molecularForm, $states) if $molecular; + return $self->equivalentTo($other, $states) || ($both && $self->molecularForm->equivalentTo($other, $states)); } # # No space in output for implied multiplication +# and add parentheses for compounds that need it # sub string { my $self = shift; - return $self->{lop}->string . $self->{rop}->string; + my ($l, $r) = ($self->{lop}->string, $self->{rop}->string); + $l = '' if $l eq '1'; + $r = "($r)" + if $l && $self->{rop}->type eq 'Compound' && ($self->{rop}{hasNumber} || $self->{lop}->class ne 'Number'); + return $l . $r; } sub TeX { my $self = shift; - return $self->{lop}->TeX . $self->{rop}->TeX; + my ($l, $r) = ($self->{lop}->TeX, $self->{rop}->TeX); + $l = '' if $l eq '1'; + $r = "($r)" + if $l && $self->{rop}->type eq 'Compound' && ($self->{rop}{hasNumber} || $self->{lop}->class ne 'Number'); + return $l . $r; } # @@ -604,7 +907,9 @@ sub TeX { # sub TYPE { my $self = shift; - return $self->{rop}->TYPE eq 'a state' ? $self->{lop}->TYPE . ' with state' : 'a compound'; + return $self->{rop}->TYPE eq 'a state' + ? $self->{lop}->TYPE . ' with state' + : 'a compound' . ($self->{hasNumber} ? ' with number' : ''); } ###################################################################### @@ -621,10 +926,33 @@ sub _check { my $self = shift; my ($lop, $rop) = ($self->{lop}, $self->{rop}); $self->Error("The left-hand side of '_' must be an element, compound, or complex, not %s", $lop->TYPE) - unless $lop->type =~ m/Element|Compound|Complex/ && !$lop->{hasState}; + unless $lop->type =~ m/Element|Compound|Complex/ && !$lop->{hasState} && !$lop->{hasNumber}; $self->Error("The right-hand side of '_' must be a number, not %s", $rop->TYPE) unless $rop->class eq 'Number'; $self->{type} = $MOLECULE->{type}; + $self->getMolecule; +} + +# +# Get the order/elements/charge data for a molecule +# +sub getMolecule { + my $self = shift; + my $lop = $self->{lop}; + my $n = $self->{rop}->eval; + $self->{order} = [ @{ $lop->{order} } ]; + $self->{elements} = { %{ $lop->{elements} } }; + $self->{elements}{$_} *= $n for (keys %{ $self->{elements} }); + $self->{charge} = $n * $lop->{charge}; + $self->{factor} = $lop->{factor}; +} + +# +# Use compound check +# +sub equivalent { + my ($self, $other) = @_; + return $self->COMPOUND->equivalent($other); } # @@ -662,11 +990,32 @@ package context::Reaction::BOP::superscript; sub _check { my $self = shift; my ($lop, $rop) = ($self->{lop}, $self->{rop}); - $self->Error("The left-hand side of '^' must be an element, molecule, or complex, not %s", $lop->TYPE) - unless $lop->type =~ m/Element|Molecule|Compound|Complex/ && !$lop->{hasState}; + $self->Error("The left-hand side of '^' must be an element, molecule, or compound, not %s", $lop->TYPE) + unless $lop->type =~ m/Element|Molecule|Compound|Complex/ && !$lop->{hasState} && !$lop->{hasNumber}; $self->Error("The right-hand side of '^' must be %s, not %s", context::Reaction::UOP->TYPE, $rop->TYPE) - unless $rop->class eq 'UOP'; + unless $rop->TYPE eq 'a charge'; $self->{type} = $ION->{type}; + $self->getIon; +} + +# +# Get the order/elements/charge data for an ion +# +sub getIon { + my $self = shift; + my $lop = $self->{lop}; + $self->{order} = [ @{ $lop->{order} } ]; + $self->{elements} = { %{ $lop->{elements} } }; + $self->{charge} = $self->{rop}->eval; + $self->{factor} = $lop->{factor}; +} + +# +# Use compound check +# +sub equivalent { + my ($self, $other) = @_; + return $self->COMPOUND->equivalent($other); } # @@ -675,7 +1024,6 @@ sub _check { sub TeX { my $self = shift; my $left = $self->{lop}->TeX; - $left = "\\left($left\\right)" if $self->{lop}->type eq 'Compound'; return $left . '^{' . $self->{rop}->TeX . '}'; } @@ -686,7 +1034,6 @@ sub string { my $self = shift; my $left = $self->{lop}->string; my $right = $self->{rop}->string; - $left = "($left)" if $self->{lop}->type eq 'Compound'; $right = "($right)" unless $right eq '-' || $right eq '+'; return "$left^$right"; } @@ -698,7 +1045,7 @@ sub string { # General unary operator (minus and plus are subclasses of this). # package context::Reaction::UOP; -our @ISA = ('Parser::UOP'); +our @ISA = ('Parser::UOP', 'context::Reaction::common'); sub _check { my $self = shift; @@ -709,13 +1056,6 @@ sub _check { $self->Error("Can't use unary '%s' with %s", $name, $self->{op}->TYPE); } -# -# Unary operators produce numbers -# -sub isChemical {0} - -sub eval { context::Reaction::eval(@_) } - # # Two nodes are equivalent if their operands are equivalent # and they have the same operator @@ -731,23 +1071,35 @@ sub equivalent { return $self->{op}->equivalent($other->{op}); } +# +# Don't allow reduction of UOP plus (or minus) +# +sub isNeg {1} + # # Always put signs on the right # sub string { - my $self = shift; - my $n = $self->{op}->string($uop->{precedence}); - return ($n eq '1' ? '' : $n) . $self->{def}{string}; + my $self = shift; + my $uop = $self->{def}; + my $op = $uop->{string}; + my $isUnity = $self->context->flags->get('showUnity'); + my $mag = $self->{op}->string($uop->{precedence}); #magnitude + $mag = '' if ($mag eq '1' && !$isUnity); + return $mag . $op; } # # Always put signs on the right # sub TeX { - my $self = shift; - my $uop = $self->{def}; - my $op = (defined($uop->{TeX}) ? $uop->{TeX} : $uop->{string}); - return $self->{op}->TeX($uop->{precedence}) . $op; + my $self = shift; + my $uop = $self->{def}; + my $op = (defined($uop->{TeX}) ? $uop->{TeX} : $uop->{string}); + my $mag = $self->{op}->TeX($uop->{precedence}); + my $isUnity = $self->context->flags->get('showUnity'); + $mag = '' if ($mag eq '1' && !$isUnity); + return $mag . $op; } sub TYPE {'a charge'} @@ -759,6 +1111,8 @@ sub TeX { package context::Reaction::UOP::minus; our @ISA = ('context::Reaction::UOP'); +sub eval { -(shift->{op}{value}) } + ###################################################################### # # Positive numbers (for ion exponents) @@ -766,12 +1120,14 @@ package context::Reaction::UOP::minus; package context::Reaction::UOP::plus; our @ISA = ('context::Reaction::UOP'); +sub eval { shift->{op}{value} } + ###################################################################### # # Implements sums of compounds as a list # package context::Reaction::List::List; -our @ISA = ('Parser::List::List'); +our @ISA = ('Parser::List::List', 'context::Reaction::common'); # # Two sums are equivalent if their terms agree in any order. @@ -841,9 +1197,19 @@ sub TeX { return join(' + ', @coords); } +# +# Get the molecular form of each item in the list +# +sub molecularForm { + my $self = shift; + my $coords = [ map { $_->molecularForm } @{ $self->{coords} } ]; + my $paren = $self->context->parens->get('('); + return $self->new($self->{equation}, $coords, 0, $paren); +} + sub eval { context::Reaction::eval(@_) } -sub isChemical {1} +sub isSum {1} sub TYPE {'a sum of compounds'} @@ -852,7 +1218,7 @@ sub TeX { # Implements complexes as a list # package context::Reaction::List::Complex; -our @ISA = ('Parser::List::List'); +our @ISA = ('Parser::List::List', 'context::Reaction::common'); sub _check { my $self = shift; @@ -862,6 +1228,16 @@ sub _check { $self->Error("The contents of a complex must be an element, molecule, ion, or compound, not %s", $arg->TYPE) unless $arg->type =~ /Element|Molecule|Ion|Compound/ && !$arg->{hasState}; $self->{type} = $COMPLEX->{type}; + $self->getComplex; +} + +sub getComplex { + my $self = shift; + $self->{order} = []; + $self->{elements} = {}; + $self->{charge} = 0; + $self->{factor} = 1; + $self->combineData($self->{coords}[0]); } # @@ -875,6 +1251,14 @@ sub equivalent { return $self->{coords}[0]->equivalent($other->{coords}[0]); } +# +# Get the molecular form of each item in the list +# +sub molecularForm { + my $self = shift; + return $self->{coords}[0]->molecularForm; +} + sub eval { context::Reaction::eval(@_) } sub isChemical {1} diff --git a/macros/contexts/contextScientificNotation.pl b/macros/contexts/contextScientificNotation.pl index 1a946bfa0b..dc030347dc 100644 --- a/macros/contexts/contextScientificNotation.pl +++ b/macros/contexts/contextScientificNotation.pl @@ -197,7 +197,7 @@ package ScientificNotation::BOP::x; # # Check that the operand types are compatible, and give -# approrpiate error messages if not. (We have to work +# appropriate error messages if not. (We have to work # hard to make a good message about the number of # decimal digits required.) # diff --git a/macros/contexts/contextTF.pl b/macros/contexts/contextTF.pl index 802f2266ab..a52e16ba66 100644 --- a/macros/contexts/contextTF.pl +++ b/macros/contexts/contextTF.pl @@ -1,7 +1,7 @@ =head1 NAME -contextTF.pl - Imlements contexts for true/false problems. +contextTF.pl - Implements contexts for true/false problems. =head1 DESCRIPTION diff --git a/macros/contexts/contextUnits.pl b/macros/contexts/contextUnits.pl index c85bf6edb4..f46bbf076f 100644 --- a/macros/contexts/contextUnits.pl +++ b/macros/contexts/contextUnits.pl @@ -293,7 +293,7 @@ =head2 Creating unit and number-with-unit objects =head2 Working with numbers with units Because units and numbers with units are full-fledged MathObjects, you -can do computations with them, just as with other MathObejcts. For +can do computations with them, just as with other MathObjects. For example, you can do $n = Compute("3 m + 10 cm"); @@ -475,7 +475,7 @@ =head2 Answer checking for units and numbers with units correct. When C<< exactUnits => 1 >> is set, however, in addition to using the units in the same order, the student's answer must use the same form (either fraction or negative power) for units in the -denominator, and will only get the C value for using +denominator, and will only get the C value for using the other form. Answers that are numbers with units are treated in a similar manner, @@ -1028,7 +1028,7 @@ sub new { dunits => {}, nfunds => $nfunds, dfunds => $dfunds, - isConsant => 1, + isConstant => 1, }, $class ); @@ -2123,8 +2123,8 @@ sub _call { # Differentiate a function with a number-with-units as an argument. # # Get the argument as a Formula. -# If the the argument is an angle, get its quantity (which includes -# the unit factor) and differentiate that. +# If the argument is an angle, get its quantity (which includes the unit factor) +# and differentiate that. # Otherwise, remove the unit from the function call and differentiate that. # sub D { diff --git a/macros/contexts/legacyFraction.pl b/macros/contexts/legacyFraction.pl index 6972174ec8..7230e8342b 100644 --- a/macros/contexts/legacyFraction.pl +++ b/macros/contexts/legacyFraction.pl @@ -10,7 +10,7 @@ =head1 DESCRIPTION reducing the fractions, and for allowing fractions with a whole-number preceding it, as in C<4 1/2> for "four and one half". The answer checker can require that students reduce their results, and there are -contexts that don't allow entery of decimal values (only fractions), +contexts that don't allow entry of decimal values (only fractions), and that don't allow any operators or functions (other than division and negation). @@ -47,7 +47,7 @@ =head1 DESCRIPTION permissible to enter a whole number WITH a fraction, as in C<2 1/2> for "two and one half", or C<5/2>. -The fourth is the same as LimiteFraction, but students must enter proper +The fourth is the same as LimitedFraction, but students must enter proper fractions, and results are shown as proper fractions. You can use the C function to generate fraction objects, or @@ -149,7 +149,7 @@ =head1 DESCRIPTION =item S>> This determines whether a space between a whole number and a fraction -is interpretted as implicit multiplication (as it usually would be in +is interpreted as implicit multiplication (as it usually would be in WeBWorK), or as addition, allowing "4 1/2" to mean "4 and 1/2". By default, it acts as multiplication in the Fraction and C contexts, and as addition in C. If @@ -182,7 +182,7 @@ =head1 DESCRIPTION This determines whether fractions MUST be entered as pure fractions rather than mixed numbers. If C is also set, then -mixed numbers will be properly interpretted, but will produce a +mixed numbers will be properly interpreted, but will produce a warning message and be marked incorrect; that is, C<2 3/4> would be recognized as C<2+3/4> rather than C<2*3/4>, but would generate a message indicating that mixed numbers are not allowed. This flag is off by @@ -482,7 +482,7 @@ sub _eval { # # If the implied multiplication represents a proper fraction with a -# preceeding integer, then switch to the proper fraction operator +# preceding integer, then switch to the proper fraction operator # (for proper handling of string() and TeX() calls), otherwise, # convert the object to a standard multiplication. # @@ -854,7 +854,7 @@ sub power { } return $self->inherit($other)->make($x, $y) unless $x eq 'nan' || $y eq 'nan'; Value::Error("Can't raise a negative number to a non-integer power") if $a * $b < 0; - Value::Error("Result of exponention is not a number"); + Value::Error("Result of exponentiation is not a number"); } sub compare { diff --git a/macros/core/PGML.pl b/macros/core/PGML.pl index cbeb92fabd..9fe50219d5 100644 --- a/macros/core/PGML.pl +++ b/macros/core/PGML.pl @@ -270,8 +270,8 @@ sub Terminate { } $self->{block} = $prev; if ($block->{stack}) { - if (scalar(@{ $block->{stack} }) == 0) { $prev->popItem } - elsif ($block->{combine}) { $prev->combineTopItems } + if (!$block->{isContainer} && scalar(@{ $block->{stack} }) == 0) { $prev->popItem } + elsif ($block->{combine}) { $prev->combineTopItems } } } @@ -1266,43 +1266,22 @@ sub format { } sub string { - my $self = shift; - my $block = shift; - my @strings = (); - my $string; - foreach my $item (@{ $block->{stack} }) { - $self->{item} = $item; - $self->{nl} = (!defined($strings[-1]) || $strings[-1] =~ m/\n$/ ? "" : "\n"); - for ($item->{type}) { - /indent/ && do { $string = $self->Indent($item); last }; - /align/ && do { $string = $self->Align($item); last }; - /par/ && do { $string = $self->Par($item); last }; - /list/ && do { $string = $self->List($item); last }; - /bullet/ && do { $string = $self->Bullet($item); last }; - /text/ && do { $string = $self->Text($item); last }; - /variable/ && do { $string = $self->Variable($item, $block); last }; - /command/ && do { $string = $self->Command($item); last }; - /math/ && do { $string = $self->Math($item); last }; - /answer/ && do { $string = $self->Answer($item); last }; - /bold/ && do { $string = $self->Bold($item); last }; - /italic/ && do { $string = $self->Italic($item); last }; - /heading/ && do { $string = $self->Heading($item); last }; - /quote/ && do { $string = $self->Quote($item, $strings[-1] || ''); last }; - /rule/ && do { $string = $self->Rule($item); last }; - /code/ && do { $string = $self->Code($item); last }; - /pre/ && do { $string = $self->Pre($item); last }; - /verbatim/ && do { $string = $self->Verbatim($item); last }; - /image/ && do { $string = $self->Image($item); last }; - /break/ && do { $string = $self->Break($item); last }; - /forced/ && do { $string = $self->Forced($item); last }; - /comment/ && do { $string = $self->Comment($item); last }; - /table/ && do { $string = $self->Table($item); last }; - /tag/ && do { $string = $self->Tag($item); last }; - PGML::Warning "Warning: unknown block type '$item->{type}' in " . ref($self) . "::format\n"; + my ($self, $block) = @_; + my $stack = $block->{stack}; + my @strings; + my $state = { block => $block, strings => \@strings, i => 0 }; + while ($state->{i} < @$stack) { + $state->{item} = $stack->[ $state->{i}++ ]; + $self->{nl} = (!defined($strings[-1]) || $strings[-1] =~ m/\n$/ ? '' : "\n"); + my $method = ucfirst($state->{item}{type}); + if (Value::can($self, $method)) { + my $string = $self->$method($state); + push(@strings, $string) unless !defined $string || $string eq ''; + } else { + PGML::Warning "Warning: unknown block type '$state->{item}{type}' in " . ref($self) . "::format\n"; } - push(@strings, $string) unless (!defined $string || $string eq ''); } - $self->{nl} = (!defined($strings[-1]) || $strings[-1] =~ m/\n$/ ? "" : "\n"); + $self->{nl} = (!defined($strings[-1]) || $strings[-1] =~ m/\n$/ ? '' : "\n"); return join('', @strings); } @@ -1313,7 +1292,7 @@ sub nl { return $nl; } -sub Escape { shift; shift } +sub escape { shift; shift } sub Indent { return "" } sub Align { return "" } @@ -1333,8 +1312,8 @@ sub nl { sub Comment { return "" } sub Table { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; return "[misplaced $item->{type}]" if $item->{hasWarning}; my @options; foreach my $option (@{ $item->{options} || [] }) { @@ -1344,7 +1323,7 @@ sub Table { my $table = []; my $row = []; for my $cell (@{ $item->{stack} }) { - push(@$row, $self->Table($cell)); + push(@$row, $self->Table({ item => $cell })); if ($cell->{hasStar}) { push(@$table, $row); $row = []; @@ -1355,13 +1334,13 @@ sub Table { } sub Tag { - my ($self, $item) = @_; - return $self->string($item); + my ($self, $state) = @_; + return $self->string($state->{item}); } sub Math { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $math = $item->{text}; if ($item->{parsed}) { my $context = $main::context{Typeset}; @@ -1386,8 +1365,8 @@ sub Math { } sub Answer { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $ans = $item->{answer}; my $rule; $item->{width} = length($item->{token}) - 2 if (!defined($item->{width})); @@ -1456,34 +1435,33 @@ sub Answer { } sub Command { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $text = $self->{parser}->replaceCommand($item); $text = PGML::LaTeX($text) if ($item->{hasStar} || 0) == 3; - $text = $self->Escape($text) unless $item->{hasStar}; + $text = $self->escape($text) unless $item->{hasStar}; return $text; } sub Variable { - my $self = shift; - my $item = shift; - my $cur = shift; - my $text = $self->{parser}->replaceVariable($item, $cur); + my ($self, $state) = @_; + my $item = $state->{item}; + my $text = $self->{parser}->replaceVariable($item, $state->{block}); $text = PGML::LaTeX($text) if ($item->{hasStar} || 0) == 3; - $text = $self->Escape($text) unless $item->{hasStar}; + $text = $self->escape($text) unless $item->{hasStar}; return $text; } sub Text { - my $self = shift; - my $item = shift; - my $text = $self->{parser}->replaceText($item); + my ($self, $state) = @_; + my $text = $self->{parser}->replaceText($state->{item}); $text =~ s/^\n+// if substr($text, 0, 1) eq "\n" && $self->nl eq ""; - return $self->Escape($text); + return $self->escape($text); } sub Image { - my ($self, $item) = @_; + my ($self, $state) = @_; + my $item = $state->{item}; my $text = $item->{text}; my $source = $item->{source}; my $width = $item->{width} || ''; @@ -1508,10 +1486,9 @@ sub Image { package PGML::Format::html; our @ISA = ('PGML::Format'); -sub Escape { - my $self = shift; - my $string = shift; - return "" unless defined $string; +sub escape { + my ($self, $string) = @_; + return '' unless defined $string; $string =~ s/&/\&/g; $string =~ s//>/g; @@ -1523,120 +1500,116 @@ sub Escape { } sub Indent { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; return $self->string($item) if $item->{indent} == 0; - my $em = 2.25 * $item->{indent}; - return - $self->nl - . '
    ' . "\n" - . $self->string($item) - . $self->nl - . "
    \n"; + my $em = 2.25 * $item->{indent}; + my $bottom = '0'; + my $next = $state->{block}{stack}[ $state->{i} ]; + if (defined $next && $next->{type} eq 'par') { + $bottom = '1em'; + $state->{i}++; + } + return main::tag('div', style => "margin:0 0 $bottom ${em}em", $self->string($item)); } sub Align { - my $self = shift; - my $item = shift; - return - $self->nl - . '
    ' . "\n" - . $self->string($item) - . $self->nl - . "
    \n"; + my ($self, $state) = @_; + my $item = $state->{item}; + return main::tag('div', style => "text-align:$item->{align};margin:0", $self->string($item)); } our %bullet = ( - bullet => 'ul type="disc"', - numeric => 'ol type="1"', - alpha => 'ol type="a"', - Alpha => 'ol type="A"', - roman => 'ol type="i"', - Roman => 'ol type="I"', - disc => 'ul type="disc"', - circle => 'ul type="circle"', - square => 'ul type="square"', + bullet => [ 'ul', 'list-style-type:disc;' ], + numeric => [ 'ol', 'list-style-type:decimal;' ], + alpha => [ 'ol', 'list-style-type:lower-alpha;' ], + Alpha => [ 'ol', 'list-style-type:upper-alpha;' ], + roman => [ 'ol', 'list-style-type:lower-roman;' ], + Roman => [ 'ol', 'list-style-type:upper-roman;' ], + disc => [ 'ul', 'list-style-type:disc;' ], + circle => [ 'ul', 'list-style-type:circle;' ], + square => [ 'ul', 'list-style-type:square;' ], ); sub List { - my $self = shift; - my $item = shift; - my $list = $bullet{ $item->{bullet} }; - return - $self->nl . '<' - . $list - . ' style="margin:0; padding-left:2.25em">' . "\n" - . $self->string($item) - . $self->nl . "\n"; + my ($self, $state) = @_; + my $item = $state->{item}; + my $list = $bullet{ $item->{bullet} }; + my $margin = '0'; + my $next = $state->{block}{stack}[ $state->{i} ]; + if (defined $next && $next->{type} eq 'par') { + $margin = '0 0 1em'; + $state->{i}++; + } + return main::tag($list->[0], style => "margin:$margin;padding-left:2.25em;$list->[1]", $self->string($item)); } sub Bullet { - my $self = shift; - my $item = shift; - return $self->nl . '
  • ' . $self->string($item) . "
  • \n"; + my ($self, $state) = @_; + my $next = $state->{block}{stack}[ $state->{i} ]; + my $style; + if (defined $next && $next->{type} eq 'par') { + $state->{i}++; + $style = 'margin-bottom:1em'; + } + return main::tag('li', $style ? (style => $style) : (), $self->string($state->{item})); } sub Code { - my $self = shift; - my $item = shift; - my $class = ($item->{class} ? ' class="' . $item->{class} . '"' : ""); - return $self->nl . '
    ' . $self->string($item) . "
    \n"; + my ($self, $state) = @_; + my $item = $state->{item}; + return main::tag( + 'pre', + style => 'margin:0', + main::tag('code', $item->{class} ? (class => $item->{class}) : (), $self->string($item)) + ); } sub Pre { - my $self = shift; - my $item = shift; - return $self->nl . '
    ' . $self->string($item) . "
    \n"; + my ($self, $state) = @_; + return main::tag('pre', style => 'margin:0', main::tag('code', $self->string($state->{item}))); } sub Heading { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $n = $item->{n}; my $text = $self->string($item); $text =~ s/^ +| +$//gm; - $text =~ s! +(
    )!$1!g; - return '' . $text . "\n"; + $text =~ s! +(
    )!$1!g; + return main::tag("h$n", style => 'margin:0', $text); } sub Par { - my $self = shift; - my $item = shift; - return $self->nl . '
    ' . "\n"; + my ($self, $state) = @_; + return main::tag('div', style => 'margin-top:1em', ''); } -sub Break {"
    \n"} +sub Break {'
    '} sub Bold { - my $self = shift; - my $item = shift; - return '' . $self->string($item) . ''; + my ($self, $state) = @_; + return main::tag('strong', $self->string($state->{item})); } sub Italic { - my $self = shift; - my $item = shift; - return '' . $self->string($item) . ''; + my ($self, $state) = @_; + return main::tag('em', $self->string($state->{item})); } our %openQuote = ('"' => "“", "'" => "‘"); our %closeQuote = ('"' => "”", "'" => "’"); sub Quote { - my $self = shift; - my $item = shift; - my $string = shift; - return $openQuote{ $item->{token} } if $string eq "" || $string =~ m/(^|[ ({\[\s])$/; - return $closeQuote{ $item->{token} }; + my ($self, $state) = @_; + my $string = $state->{strings}[-1] // ''; + return $openQuote{ $state->{item}{token} } if $string eq '' || $string =~ m/(^|[ ({\[\s])$/; + return $closeQuote{ $state->{item}{token} }; } sub Rule { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $width = '100%;'; my $height = '1px'; if (defined $item->{width}) { @@ -1650,34 +1623,34 @@ sub Rule { $height = $item->{size}; $height .= 'px' if ($height =~ /^\d*\.?\d+$/); } - return $self->nl - . main::tag( + return main::tag( + 'div', + main::tag( 'div', + style => "width:$width;display:inline-block;margin:0.3em auto", main::tag( - 'span', - style => "width:$width; display:inline-block; margin:0.3em auto", - main::tag( - 'hr', style => "width:$width; height:$height; background-color:currentColor; margin:0.3em auto;" - ) + 'hr', style => "width:$width;height:$height;background-color:currentColor;margin:0.3em auto;" ) - ); + ) + ); } sub Verbatim { - my $self = shift; - my $item = shift; - my $text = $self->Escape($item->{text}); - $text = "$text" if $item->{hasStar}; + my ($self, $state) = @_; + my $item = $state->{item}; + my $text = $self->escape($item->{text}); + $text = main::tag('code', $text) if $item->{hasStar}; return $text; } sub Math { - my $self = shift; - return main::general_math_ev3($self->SUPER::Math(@_)); + my ($self, $state) = @_; + return main::general_math_ev3($self->SUPER::Math($state)); } sub Tag { - my ($self, $item) = @_; + my ($self, $state) = @_; + my $item = $state->{item}; my %whitelist = (div => 1, span => 1); my @attributes = ref($item->{html}) eq 'ARRAY' ? @{ $item->{html} } : $item->{html}; my $tag = @attributes % 2 ? (shift @attributes // 'div') : 'div'; @@ -1720,32 +1693,31 @@ package PGML::Format::tex; '~' => '{\ttfamily\char126}', ); -sub Escape { - my $self = shift; - my $string = shift; - return "" unless defined($string); +sub escape { + my ($self, $string) = @_; + return '' unless defined($string); $string =~ s/(["\#\$%&<>\\^_\{|\}~])/$escape{$1}/eg; return $string; } sub Indent { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; return $self->string($item) if $item->{indent} == 0; my $em = 2.25 * $item->{indent}; return $self->nl . "{\\pgmlIndent\n" . $self->string($item) . $self->nl . "\\par}%\n"; } sub Align { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $align = uc(substr($item->{align}, 0, 1)) . substr($item->{align}, 1); return $self->nl . "{\\pgml${align}{}" . $self->string($item) . $self->nl . "\\par}%\n"; } sub List { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; return $self->nl . "{\\pgmlIndent\\let\\pgmlItem=\\pgml$item->{bullet}Item\n" @@ -1755,26 +1727,23 @@ sub List { } sub Bullet { - my $self = shift; - my $item = shift; - return $self->nl . "\\pgmlItem{}" . $self->string($item) . "\n"; + my ($self, $state) = @_; + return $self->nl . "\\pgmlItem{}" . $self->string($state->{item}) . "\n"; } sub Code { - my $self = shift; - my $item = shift; - return $self->nl . "{\\pgmlPreformatted\\ttfamily%\n" . $self->string($item) . "\\par}%\n"; + my ($self, $state) = @_; + return $self->nl . "{\\pgmlPreformatted\\ttfamily%\n" . $self->string($state->{item}) . "\\par}%\n"; } sub Pre { - my $self = shift; - my $item = shift; - return $self->nl . "{\\pgmlPreformatted%\n" . $self->string($item) . "\\par}%\n"; + my ($self, $state) = @_; + return $self->nl . "{\\pgmlPreformatted%\n" . $self->string($state->{item}) . "\\par}%\n"; } sub Heading { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $n = $item->{n}; my $text = $self->string($item); $text =~ s/^ +| +$//gm; @@ -1783,39 +1752,35 @@ sub Heading { } sub Par { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; return $self->nl . "\\vskip\\baselineskip\n"; } sub Break {"\\pgmlBreak\n"} sub Bold { - my $self = shift; - my $item = shift; - return "{\\bfseries{}" . $self->string($item) . "}"; + my ($self, $state) = @_; + return "{\\bfseries{}" . $self->string($state->{item}) . "}"; } sub Italic { - my $self = shift; - my $item = shift; - return "{\\itshape{}" . $self->string($item) . "}"; + my ($self, $state) = @_; + return "{\\itshape{}" . $self->string($state->{item}) . "}"; } our %openQuote = ('"' => "``", "'" => "`"); our %closeQuote = ('"' => "''", "'" => "'"); sub Quote { - my $self = shift; - my $item = shift; - my $string = shift; - return $openQuote{ $item->{token} } if $string eq "" || $string =~ m/(^|[ ({\[\s])$/; - return $closeQuote{ $item->{token} }; + my ($self, $state) = @_; + my $string = $state->{strings}[-1] // ''; + return $openQuote{ $state->{item}{token} } if $string eq '' || $string =~ m/(^|[ ({\[\s])$/; + return $closeQuote{ $state->{item}{token} }; } sub Rule { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $width = "100%"; my $height = "1"; $width = $item->{width} if defined $item->{width}; @@ -1829,20 +1794,21 @@ sub Rule { } sub Verbatim { - my $self = shift; - my $item = shift; - my $text = $self->Escape($item->{text}); + my ($self, $state) = @_; + my $item = $state->{item}; + my $text = $self->escape($item->{text}); $text = "{\\tt{}$text}" if $item->{hasStar}; return $text; } sub Math { - my $self = shift; - return main::general_math_ev3($self->SUPER::Math(@_)); + my ($self, $state) = @_; + return main::general_math_ev3($self->SUPER::Math($state)); } sub Tag { - my ($self, $item) = @_; + my ($self, $state) = @_; + my $item = $state->{item}; my ($tex_begin, $tex_end); if (ref($item->{tex}) eq 'ARRAY') { ($tex_begin, $tex_end) = @{ $item->{tex} }; @@ -1858,10 +1824,9 @@ sub Tag { package PGML::Format::ptx; our @ISA = ('PGML::Format'); -sub Escape { - my $self = shift; - my $string = shift; - return "" unless defined $string; +sub escape { + my ($self, $string) = @_; + return '' unless defined $string; $string =~ s/&/&/g; $string =~ s//>/g; @@ -1870,15 +1835,14 @@ sub Escape { # No indentation for PTX sub Indent { - my $self = shift; - my $item = shift; - return $self->string($item); + my ($self, $state) = @_; + return $self->string($state->{item}); } # No align for PTX sub Align { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; return "\n" . $self->string($item); } @@ -1895,21 +1859,20 @@ sub Align { ); sub List { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $list = $bullet{ $item->{bullet} }; return $self->nl . '<' . $list . '>' . "\n" . $self->string($item) . $self->nl . "\n"; } sub Bullet { - my $self = shift; - my $item = shift; - return $self->nl . '
  • ' . $self->string($item) . '
  • '; + my ($self, $state) = @_; + return $self->nl . '
  • ' . $self->string($state->{item}) . '
  • '; } sub Code { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $code = ($self->string($item) =~ /\n/) ? $self->nl @@ -1925,8 +1888,7 @@ sub Code { } sub Pre { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; ## PGML pre can have stylized contents like bold, ## and PTX pre cannot have element children return "\n"; @@ -1934,52 +1896,47 @@ sub Pre { # PreTeXt can't use headings. sub Heading { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; return "\n" . $self->string($item); } sub Par { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; return $self->nl . "\n"; } sub Break {"\n\n"} sub Bold { - my $self = shift; - my $item = shift; - return '' . $self->string($item) . ''; + my ($self, $state) = @_; + return '' . $self->string($state->{item}) . ''; } sub Italic { - my $self = shift; - my $item = shift; - return '' . $self->string($item) . ''; + my ($self, $state) = @_; + return '' . $self->string($state->{item}) . ''; } our %openQuote = ('"' => "", "'" => ""); our %closeQuote = ('"' => "", "'" => ""); sub Quote { - my $self = shift; - my $item = shift; - my $string = shift; - return $openQuote{ $item->{token} } if $string eq "" || $string =~ m/(^|[ ({\[\s])$/; - return $closeQuote{ $item->{token} }; + my ($self, $state) = @_; + my $string = $state->{strings}[-1] // ''; + return $openQuote{ $state->{item}{token} } if $string eq '' || $string =~ m/(^|[ ({\[\s])$/; + return $closeQuote{ $state->{item}{token} }; } # No rule for PTX sub Rule { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; return "\n"; } sub Verbatim { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $text = $item->{text}; if ($item->{hasStar}) { #Don't escape most content. Just < and & @@ -1995,12 +1952,13 @@ sub Verbatim { } sub Math { - my $self = shift; - return main::general_math_ev3($self->SUPER::Math(@_)); + my ($self, $state) = @_; + return main::general_math_ev3($self->SUPER::Math($state)); } sub Tag { - my ($self, $item) = @_; + my ($self, $state) = @_; + my $item = $state->{item}; my @args = ref($item->{ptx}) eq 'ARRAY' ? @{ $item->{ptx} } : $item->{ptx}; if (my $tag = shift @args) { return NiceTables::tag($self->string($item), $tag, @args); diff --git a/macros/core/PGanswermacros.pl b/macros/core/PGanswermacros.pl index d858e432c1..fbe899f44a 100644 --- a/macros/core/PGanswermacros.pl +++ b/macros/core/PGanswermacros.pl @@ -116,7 +116,7 @@ =head1 DESCRIPTION BEGIN { strict->import; } -# Until we get the PG cacheing business sorted out, we need to use +# Until we get the PG caching business sorted out, we need to use # PG_restricted_eval to get the correct values for some(?) PG environment # variables. We do this once here and place the values in lexicals for later # access. @@ -222,7 +222,7 @@ sub get_var_array { } ## Internal routine that converts limits into the standard array of arrays format -## Some of the cases are probably unneccessary, but better safe than sorry +## Some of the cases are probably unnecessary, but better safe than sorry ## ## IN: one of the following: ## an undefined value (i.e., no limits were specified) @@ -463,7 +463,7 @@ =head3 std_num_array_filter Assumes the {student_ans} field is a numerical array, and applies BOTH check_syntax and std_num_filter to each element of the array. Does it's best to generate sensible error messages for syntax errors. -A typical error message displayed in {studnet_ans} might be ( 56, error message, -4). +A typical error message displayed in {student_ans} might be ( 56, error message, -4). =cut @@ -615,7 +615,7 @@ sub is_zero_array { 'stdin' => 'ra_differences', 'stdout' => 'score', ); - #intialize + #initialize $rh_ans->{_filter_name} = $options{_filter_name}; my $array = $rh_ans->{ $options{stdin} }; # default ra_differences @@ -660,7 +660,7 @@ =head2 C in the field {ra_parameters}. -The last $dim_of_parms_space variables are assumed to be parameters, and it is also +The last $dim_of_params_space variables are assumed to be parameters, and it is also assumed that the function \&comparison_fun depends linearly on these variables. This function finds the values for these parameters which minimizes the Euclidean distance (L2 distance) between the test function and the comparison function and the test points specified @@ -697,11 +697,11 @@ sub best_approx_parameters { my %options = @_; set_default_options( \%options, - '_filter_name' => 'best_approx_paramters', + '_filter_name' => 'best_approx_parameters', 'allow_unknown_options' => 1, ); my $errors = undef; - # This subroutine for the determining the coefficents of the parameters at a given point + # This subroutine for the determining the coefficients of the parameters at a given point # is pretty specialized, so it is included here as a sub-subroutine. my $determine_param_coeffs = sub { my ($rf_fun, $ra_variables, $dim_of_params_space, %options) = @_; @@ -1110,7 +1110,7 @@ sub phase_pi { $rh_ans; } -=head4 C +=head4 C =cut @@ -1297,7 +1297,7 @@ sub check_strings { # OVERVIEW of reminder of function: # if answer is correct, return correct. (adjust score to 1) -# if answer is incorect: +# if answer is incorrect: # 1) determine if the answer is sensible. if it is, return incorrect. # 2) if the answer is not sensible (and incorrect), then return an error message indicating so. # no matter what: throw a 'STRING' error to skip numerical evaluations. (error flag skips remainder of pre_filters and evaluators) @@ -1638,8 +1638,6 @@ sub avg_problem_grader { my %problem_result = (score => 0, errors => '', type => 'avg_problem_grader', msg => ''); - $problem_result{msg} = maketext('You can earn partial credit on this problem.') if keys %$answers > 1; - # Return unless answers have been submitted. return (\%problem_result, $problem_state) unless $form_options{answers_submitted} == 1; @@ -1724,7 +1722,7 @@ =head3 C # if ( not ref($r_input) ) { # $out = $r_input if defined $r_input; # not a reference # $out =~ s/"; diff --git a/macros/core/PGbasicmacros.pl b/macros/core/PGbasicmacros.pl index 8e0ca91060..3258e2afda 100644 --- a/macros/core/PGbasicmacros.pl +++ b/macros/core/PGbasicmacros.pl @@ -223,7 +223,7 @@ =head2 Answer blank macros: When entering radio buttons using the "NAMED" format, you should use NAMED_ANS_RADIO button for the first button and then use NAMED_ANS_RADIO_EXTENSION for the remaining buttons. NAMED_ANS_RADIO requires a -matching answer evalutor, while NAMED_ANS_RADIO_EXTENSION does not. The name +matching answer evaluator, while NAMED_ANS_RADIO_EXTENSION does not. The name used for NAMED_ANS_RADIO_EXTENSION should match the name used for NAMED_ANS_RADIO (and the associated answer evaluator). @@ -790,7 +790,7 @@ =head2 answer_matrix text which represents the matrix in TeX format used in math display mode. Answers are then passed back to whatever answer evaluators you write at the end of the problem. (note, if you have an m x n matrix, you will need mn answer evaluators, and they will be -returned to the evaluaters starting in the top left hand corner and proceed to the left +returned to the evaluators starting in the top left hand corner and proceed to the left and then at the end moving down one row, just as you would read them.) The options are passed on to display_matrix. @@ -961,7 +961,7 @@ sub SOLUTION { . tag( 'div', class => 'accordion-collapse collapse', - tag('div', class => 'accordion-body', $solution_body) + tag('div', class => 'accordion-body overflow-x-auto', $solution_body) ) ) )); @@ -1003,7 +1003,7 @@ sub HINT { . tag( 'div', class => 'accordion-collapse collapse', - tag('div', class => 'accordion-body', $hint_body) + tag('div', class => 'accordion-body overflow-x-auto', $hint_body) ) ) )); @@ -1043,7 +1043,7 @@ =head2 Comments to instructors in the Library Browser below the rendered problem. The function COMMENT stores the needed html in the variable -pgComment, which gets transfered to the flag 'comment' in PG_FLAGS. +pgComment, which gets transferred to the flag 'comment' in PG_FLAGS. =cut @@ -1260,11 +1260,11 @@ sub BRBR { sub EM { MODES(TeX => '\\)', HTML_MathJax => '\\)', HTML => '', PTX => ''); }; # end math mode sub BDM { - MODES(TeX => '\\[', HTML_MathJax => '\\[', HTML => '

    ', PTX => ''); + MODES(TeX => '\\[', HTML_MathJax => '\\[', HTML => '

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

    ', PTX => ''); + MODES(TeX => '\\]', HTML_MathJax => '\\]', HTML => '

    ', PTX => ''); }; #end displayMath mode sub LTS { @@ -1319,8 +1319,8 @@ sub SPACE { sub EBOLD { MODES(TeX => '}', HTML => '', PTX => ''); } sub BLABEL { MODES(TeX => '', HTML => '', PTX => ''); } -sub BITALIC { MODES(TeX => '{\\it ', HTML => '', PTX => ''); } -sub EITALIC { MODES(TeX => '} ', HTML => '', PTX => ''); } +sub BITALIC { MODES(TeX => '{\\it ', HTML => '', PTX => ''); } +sub EITALIC { MODES(TeX => '} ', HTML => '', PTX => ''); } sub BUL { MODES(TeX => '\\underline{', HTML => '', PTX => ''); } sub EUL { MODES(TeX => '}', HTML => '', PTX => ''); } @@ -1387,7 +1387,7 @@ sub RB { =head2 SPAN and DIV macros -These are functions primarly meant to add +These are functions primarily meant to add HTML block level DIV or inline SPAN tags and the relevant closing tags for HTML output. @@ -1485,7 +1485,7 @@ sub processDivSpanOptions { # A space is used to separate class names - # The offical W3C documentation allows class names to follow a far more general + # The official W3C documentation allows class names to follow a far more general # grammar, but this is not being permitted here at present. # See: https://www.w3.org/TR/css-syntax-3/#token-diagrams @@ -1524,7 +1524,7 @@ sub processDivSpanOptions { $StyleVal = $options{style}; - # Mininal cleanup for safety + # Minimal cleanup for safety $StyleVal =~ s/["']//g; # Drop quotes if ($StyleVal eq $options{style}) { # no quotes, so now drop other characters we consider invalid @@ -1656,7 +1656,7 @@ =head2 Evaluation macros =head3 EV3 - TEXT(EV3("This is a formulat \( \int_0^5 x^2 \, dx \) "); + TEXT(EV3("This is a formula \( \int_0^5 x^2 \, dx \) "); TEXT(EV3(@text)); TEXT(EV3(<<'END_TEXT')); @@ -1670,7 +1670,7 @@ =head3 EV3 Using EV3 with strings which have been evaluated by double quotes may lead to unexpected results. The evaluation macro E3 first evaluates perl code inside the braces: C<\{ code \}>. -Any perl statment can be put inside the braces. The +Any perl statement can be put inside the braces. The result of the evaluation (i.e. the last statement evaluated) replaces the C<\{ code \}> construction. Next interpolation of all variables (e.g. C<$var or @array> ) is performed. @@ -1790,7 +1790,7 @@ =head3 FEQ C will give a number with 3 decimal places and a negative sign if the number is negative, no sign if the number is positive. Since this is -identical to the behavior of C<{$c:%0.3f}> the use of this syntax is depricated. +identical to the behavior of C<{$c:%0.3f}> the use of this syntax is deprecated. C determines the sign and prints it whether the number is positive or negative. You can use this @@ -1864,7 +1864,7 @@ sub old_safe_ev { my ($out, $PG_eval_errors, $PG_full_error_report) = PG_restricted_eval($in); if ($PG_eval_errors) { my @errorLines = split("\n", $PG_eval_errors); - warn "There is an error occuring inside evaluation brackets \\{ ...code... \\}\n" + warn "There is an error occurring inside evaluation brackets \\{ ...code... \\}\n" . "somewhere in an EV2, EV3, or BEGIN_TEXT block.\n" . "Code evaluated:\n$in\n" . "Errors:\n" @@ -1950,7 +1950,7 @@ sub general_math_ev3 { $alignment = ($alignment eq 'align') ? '' : " alignment=\"$alignment\""; $out = "\n$rows\n"; } elsif ($mode eq 'display') { - $out = "$in"; + $out = "$in"; } } elsif ($displayMode eq "HTML") { $in_delim = HTML::Entities::encode_entities($in_delim); @@ -1988,7 +1988,7 @@ sub EV3 { $string =~ s//>/g; $evaluated_string = - "
    $PAR % ERROR in $0:EV3, PGbasicmacros.pl: $PAR % There is an error occuring in the following code:$BR $string $BR % $BR % $errorLines[0]\n % $errorLines[1]$BR % $BR % $BR 
    "; + "
    $PAR % ERROR in $0:EV3, PGbasicmacros.pl: $PAR % There is an error occurring in the following code:$BR $string $BR % $BR % $errorLines[0]\n % $errorLines[1]$BR % $BR % $BR 
    "; $@ = ""; } $string = $evaluated_string; @@ -2008,7 +2008,7 @@ sub EV4 { $string =~ s/>/>/g; $evaluated_string = "
    $PAR % ERROR in $0:EV3, PGbasicmacros.pl:"
    -				. "$PAR % There is an error occuring in the following code:$BR "
    +				. "$PAR % There is an error occurring in the following code:$BR "
     				. "$string $BR % $BR % $errorLines[0]\n % $errorLines[1]$BR "
     				. "% $BR % $BR 
    "; } @@ -2187,7 +2187,7 @@ sub PTX_cleanup { #move PTX warnings from the beginning of inside a p to just before the p. $string =~ s/

    ()/$1\n

    /g; - #remove doulbe p's we may have created + #remove double p's we may have created $string =~ s/

    /

    /g; $string =~ s/<\/p><\/p>/<\/p>/g; @@ -2423,7 +2423,7 @@ =head2 knowlLink Example usage: - knowlLink('Click Me', title => 'Fascinating Contents', value => 'Here are my facinating contents.'); + knowlLink('Click Me', title => 'Fascinating Contents', value => 'Here are my fascinating contents.'); knowlLink('Help Me', title => 'Help Contents', url => 'https://my.domain.edu/helpfile-contents'); =cut @@ -2623,7 +2623,7 @@ =head2 Sorting and other list macros Usage: - lex_sort(@list); # outputs list in lexigraphic (alphabetical) order + lex_sort(@list); # outputs list in lexicographic (alphabetical) order num_sort(@list); # outputs list in numerical order uniq( @list); # outputs a list with no duplicates. Order is unspecified. @@ -2634,13 +2634,9 @@ =head2 Sorting and other list macros # uniq gives unique elements of a list: sub uniq { - my @in = @_; - my %temp = (); - while (@in) { - $temp{ shift(@in) }++; - } - my @out = keys %temp; # sort is causing trouble with Safe.?? - @out; + my @in = @_; + my %seen; + return grep { !$seen{$_}++ } @in; } sub lex_sort { @@ -2661,7 +2657,7 @@ =head2 Macros for handling tables row(@dataelements) endtable() -Example of useage: +Example of usage: BEGIN_TEXT This problem tests calculating new functions from old ones:$BR @@ -2927,14 +2923,14 @@ sub image { ); next; } - if (ref $image_item eq 'Plots::Plot') { + if (eval { $image_item->isa('Plots::Plot') }) { # Update image attributes as needed. $image_item->{width} = $width if $out_options{width}; $image_item->{height} = $height if $out_options{height}; $image_item->{tex_size} = $tex_size if $out_options{tex_size}; - $image_item->axes->style(aria_description => shift @alt_list) if $out_options{alt}; if ($image_item->ext eq 'html') { + $image_item->axes->style(aria_description => shift @alt_list) if $out_options{alt}; $image_item->{description_details} = $description_details; push(@output_list, $image_item->draw); next; @@ -2946,10 +2942,9 @@ sub image { $width_ratio = 0.001 * $image_item->{tex_size}; } $image_item = insertGraph($image_item) - if (ref $image_item eq 'WWPlot' - || ref $image_item eq 'Plots::Plot' - || ref $image_item eq 'PGlateximage' - || ref $image_item eq 'PGtikz'); + if grep { + eval { $image_item->isa($_) } + } ('WWPlot', 'Plots::Plot', 'LaTeXImage'); my $imageURL = alias($image_item) // ''; $imageURL = ($envir{use_site_prefix}) ? $envir{use_site_prefix} . $imageURL : $imageURL; my $id = $main::PG->getUniqueName('img'); @@ -3232,7 +3227,7 @@ sub tag { ' ', map { ($_ =~ s/_/-/gr) . (defined $attributes{$_} ? ('="' . encode_pg_and_html($attributes{$_})) . '"' : '') - } + } keys %attributes ); diff --git a/macros/core/PGessaymacros.pl b/macros/core/PGessaymacros.pl index c4d6c67bf2..2dcecea5bb 100644 --- a/macros/core/PGessaymacros.pl +++ b/macros/core/PGessaymacros.pl @@ -1,7 +1,7 @@ =head1 NAME -PGessaymacros.pl - Macros for building answer evaluators. +PGessaymacros.pl - Macros for manually graded text answers. =head2 SYNOPSIS @@ -11,11 +11,18 @@ =head2 SYNOPSIS Answer Boxes: - essay_box() + essay_box(rows, columns, html_options) To use essay answers call C in your problem file wherever you want the input box to go, and then use C for the corresponding checker. -You will then need to grade the problem manually. +You will then need to grade the problem manually. You can explicitly set the +size of the essay box by specifying the number of rows and columns. Any additional +arguments will be passed as html attributes of the text area. For example + + essay_box(8, 60, maxlength => 250) + +will produce a text box with 8 rows and 60 columns that has a limit of 250 +characters. explanation_box() @@ -60,7 +67,7 @@ sub essay_cmp { $options->{resultClass} = ''; $options->{insertMethod} = 'append_content'; - $options->{btnClass} = 'btn-info'; + $options->{btnClass} = 'btn-preview'; $options->{btnAddClass} = ''; $options->{wrapPreviewInTex} = 0; $options->{showEntered} = 0; # Suppress output of the feedback entered answer. @@ -92,7 +99,7 @@ sub essay_cmp { } sub NAMED_ESSAY_BOX { - my ($name, $row, $col) = @_; + my ($name, $row, $col, %html_options) = @_; $row //= 8; $col //= 75; @@ -109,6 +116,7 @@ sub NAMED_ESSAY_BOX { TeX => qq!\\vskip $height in \\hrulefill\\quad !, HTML => tag( 'textarea', + %html_options, name => $name, id => $name, aria_label => generate_aria_label($name), @@ -137,12 +145,12 @@ sub NAMED_ESSAY_BOX { } sub essay_box { - my ($row, $col) = @_; + my ($row, $col, %html_options) = @_; $row ||= 8; $col ||= 75; my $name = NEW_ANS_NAME(); main::RECORD_IMPLICIT_ANS_NAME($name); - NAMED_ESSAY_BOX($name, $row, $col); + NAMED_ESSAY_BOX($name, $row, $col, %html_options); } diff --git a/macros/core/Parser.pl b/macros/core/Parser.pl index 646d9b588a..5c094d7e32 100644 --- a/macros/core/Parser.pl +++ b/macros/core/Parser.pl @@ -71,7 +71,7 @@ =head2 Compute if needed later in the problem. -If the formula is contstant-valued, C will return the value +If the formula is constant-valued, C will return the value of the formula rather than the formula itself. Again, in this case, the original can be obtained from the C property. diff --git a/macros/core/scaffold.pl b/macros/core/scaffold.pl index 7f86175925..d59fb669b6 100644 --- a/macros/core/scaffold.pl +++ b/macros/core/scaffold.pl @@ -167,7 +167,7 @@ =head1 DESCRIPTION section will be open automatically when the problem is first viewed, but if you have material to read (or even answers to give) prior to the first section, you might want the first section to be closed, and -have the student open it by hand before anwering the questions. In +have the student open it by hand before answering the questions. In this case, set this value to 0 (it is 1 by default). =item C<< preview_can_change_state => 0 or 1 >> @@ -245,7 +245,7 @@ =head1 DESCRIPTION you can provide a subroutine that determines when the section can or should be open. -Note that values like C<$showPartialCorrectAnswers> and the isntalled +Note that values like C<$showPartialCorrectAnswers> and the installed grader are global to the whole problem, so can't be set individually on a per section basis. Also note that the answers aren't checked until the end of the problem, so any changes you make to the @@ -539,7 +539,7 @@ sub add_container { # Scaffold to consider the score to be 1. This is used by PGessaymacros.pl. # Also, if answers are being previewed and the preview_can_change_state option is 0, then use the scores saved # in the persistent data hash form the last answer submission (if there is no data for an answer, then the - # anwser is considered blank). + # answer is considered blank). for (@{ $self->{ans_names} }) { next unless defined $PG_ANSWERS_HASH->{$_}; $scaffold->{scores}{$_} = $scaffoldScores->{$_} = diff --git a/macros/deprecated/Alfredmacros.pl b/macros/deprecated/Alfredmacros.pl index e437f371cd..796911a4eb 100755 --- a/macros/deprecated/Alfredmacros.pl +++ b/macros/deprecated/Alfredmacros.pl @@ -235,7 +235,7 @@ () ### 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" +### upper - the upper 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. @@ -250,7 +250,7 @@ () ### ### 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 +### The default limits of integration 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), diff --git a/macros/deprecated/CanvasObject.pl b/macros/deprecated/CanvasObject.pl index 1b41d5ad9d..7c315229f4 100644 --- a/macros/deprecated/CanvasObject.pl +++ b/macros/deprecated/CanvasObject.pl @@ -241,7 +241,7 @@ sub insertGridButtons { # # my $base64_initialState = ''; # encode_base64($self->initialState); # main::RECORD_FORM_LABEL($appletStateName); #this insures that the state will be saved from one invocation to the next -# # FIXME -- with PGcore the persistant data mechanism can be used instead +# # FIXME -- with PGcore the persistent data mechanism can be used instead # my $answer_value = ''; # # ########################## diff --git a/macros/deprecated/CofIdaho_macros.pl b/macros/deprecated/CofIdaho_macros.pl index 05903af8c0..86cc423b06 100755 --- a/macros/deprecated/CofIdaho_macros.pl +++ b/macros/deprecated/CofIdaho_macros.pl @@ -7,7 +7,7 @@ =head1 SYNOPSIS macros by R Cruz -- The College of Idaho -=head1 Format the instuctor answer +=head1 Format the instructor answer 1) SimplifyExponents: Formats an expression without negative exponents @@ -63,7 +63,7 @@ =head1 Format the instuctor answer 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); + To use: $a = SimplifyFraction(numerator expression,denominator expression); =cut ################################################################### @@ -366,7 +366,7 @@ sub FactoringEvaluator { my @vars = @_; my $format_ans = $ans; - $format_ans =~ s/\*/ /g; #Remove any astrix + $format_ans =~ s/\*/ /g; #Remove any asterisk $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 @@ -414,7 +414,7 @@ sub FactoringEvaluator { if ($ans_hash->{score} == 1) { my $format_student_ans = $student_ans; - $format_student_ans =~ s/[\*]/ /g; #Remove any astrix + $format_student_ans =~ s/[\*]/ /g; #Remove any asterisk $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 @@ -476,7 +476,7 @@ sub StrictFactoringEvaluator { my @vars = @_; my $format_ans = $ans; - $format_ans =~ s/[\*]//g; #Remove any astrix + $format_ans =~ s/[\*]//g; #Remove any asterisk $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 @@ -504,7 +504,7 @@ sub StrictFactoringEvaluator { 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; #Remove any asterisk $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 diff --git a/macros/deprecated/Dartmouthmacros.pl b/macros/deprecated/Dartmouthmacros.pl index 5358d21549..0542993d58 100755 --- a/macros/deprecated/Dartmouthmacros.pl +++ b/macros/deprecated/Dartmouthmacros.pl @@ -79,7 +79,7 @@ sub vector_diff { ## Compute the length of a vector sub vec_length { - ## Put the paramaters passed into an array of values + ## Put the parameters passed into an array of values my @vector = @_; ## Initialize maximum value to first element @@ -137,7 +137,7 @@ sub cross_product { ## Compute the maximum value in a list #sub max { -# ## Put the paramters passed into an array of values +# ## Put the parameters passed into an array of values # my @values = @_; # # ## Initialize maximum value to first element @@ -155,7 +155,7 @@ sub cross_product { ## Compute the minimum value in a list #sub min { -# ## Put the paramters passed into an array of values +# ## Put the parameters passed into an array of values # my @values = @_; # # ## Initialize minimum value to first element diff --git a/macros/deprecated/PGcomplexmacros.pl b/macros/deprecated/PGcomplexmacros.pl index ca21d6f7d1..97f70a9e59 100644 --- a/macros/deprecated/PGcomplexmacros.pl +++ b/macros/deprecated/PGcomplexmacros.pl @@ -52,7 +52,7 @@ =head2 cplx_cmp # functions. The default display method is cartesian, for all methods, but if # the student answer is polar, even in part, then their answer will be displayed # that way. - # 'strict_polar' This is still under developement. The idea is to check to make sure that there + # 'strict_polar' This is still under development. The idea is to check to make sure that there # only a single term in front of the e and after it... but the method does not # check to make sure that the i is in the exponent, nor does it handle cases # where the polar has e** coefficients. @@ -123,7 +123,7 @@ sub cplx_cmp { # error checking, but that was removed in version 1.9 and it had been commented out # prior to that because it was always producing errors. This is because $correct_num_answer # usually is somethine like "1+4i", which will produce a "missing operation before 'i'" - # error, and "1-i" wil produce an "amiguous use of '-i' resolved as '-&i'" message. + # error, and "1-i" will produce an "ambiguous use of '-i' resolved as '-&i'" message. # You probably need a call to check_syntax and the other filters that are used on # the student answer first. (Unless the item is already a reference to a Complex, # in which canse you should just accept it.) @@ -252,12 +252,12 @@ =head2 multi_cmp # # Checks a comma separated string of items against an array of evaluators. # For example this is useful for checking all of the complex roots of an equation. - # Each student answer must be evaluated as correct by a DISTINCT answer evalutor. + # Each student answer must be evaluated as correct by a DISTINCT answer evaluator. # # This answer checker will only work reliably if each answer checker corresponds # to a distinct correct answer. For example if one answer checker requires # any positive number, and the second requires the answer 1, then 1,2 might - # be judged incorrect since 1, satisifes the first answer checker, but 2 doesn't + # be judged incorrect since 1, satisfies the first answer checker, but 2 doesn't # satisfy the second. 2,1 would work however. Avoid this type of use!! # # Including backtracking to fit the answers as best possible to each answer evaluator @@ -355,7 +355,7 @@ =head2 Utility functions =cut -# Output is text displaying the complex numver in "e to the i theta" form. The +# Output is text displaying the complex number in "e to the i theta" form. The # formats for the argument theta is determined by the option C and the # format for the modulus is determined by the C option. @@ -488,8 +488,8 @@ sub is_a_numeric_polar { } } -#this subroutine mearly captures what is before and after the "e**" it does not verify that the "i" is there, or in the -#exponent this must eventually be addresed +#this subroutine merely captures what is before and after the "e**" it does not verify that the "i" is there, or in the +#exponent this must eventually be addressed sub is_a_polar { my ($num, %options) = @_; my $process_ans_hash = (ref($num) eq 'AnswerHash') ? 1 : 0; @@ -527,9 +527,9 @@ sub is_a_polar { =head2 single_term() - # This subroutine takes in a string, which is a mathematical expresion, and determines whether or not + # This subroutine takes in a string, which is a mathematical expression, and determines whether or not # it is a single term. This is accoplished using a stack. Open parenthesis pluses and minuses are all - # added onto the stack, and when a closed parenthesis is reached, the stack is popped untill the open + # added onto the stack, and when a closed parenthesis is reached, the stack is popped until the open # parenthesis is found. If the original was a single term, the stack should be empty after # evaluation. If there is anything left ( + or - ) then false is returned. # Of course, the unary operator "-" must be handled... if it is a unary operator, and not a regular - diff --git a/macros/deprecated/PGsequentialmacros.pl b/macros/deprecated/PGsequentialmacros.pl index 55a596135d..655810b055 100644 --- a/macros/deprecated/PGsequentialmacros.pl +++ b/macros/deprecated/PGsequentialmacros.pl @@ -55,7 +55,7 @@ =head2 listFormVariables listVariables(); Lists all variables submitted in the problem form and all variables in the -the Problem environment. This is used for debugging. +Problem environment. This is used for debugging. =cut diff --git a/macros/deprecated/PGtextevaluators.pl b/macros/deprecated/PGtextevaluators.pl index 86c053f8f3..e745281859 100644 --- a/macros/deprecated/PGtextevaluators.pl +++ b/macros/deprecated/PGtextevaluators.pl @@ -21,7 +21,7 @@ =head1 SYNOPSIS =head1 DESCRIPTION -This file contians macros for handling questionnaires. Questionnaires can +This file contains macros for handling questionnaires. Questionnaires can consist of textual answers and radio buttons, and responses are reported via email. @@ -29,7 +29,7 @@ =head1 DESCRIPTION BEGIN { strict->import; } -# Until we get the PG cacheing business sorted out, we need to use +# Until we get the PG caching business sorted out, we need to use # PG_restricted_eval to get the correct values for some(?) PG environment # variables. We do this once here and place the values in lexicals for later # access. diff --git a/macros/deprecated/compoundProblem.pl b/macros/deprecated/compoundProblem.pl index 1020f70c14..0b9e2203fb 100644 --- a/macros/deprecated/compoundProblem.pl +++ b/macros/deprecated/compoundProblem.pl @@ -146,7 +146,7 @@ # # $cp->part Returns the part the student is working on. # $cp->part(n) Sets the part to be part n, as long as the -# student has finished the preceeding parts. +# student has finished the preceding parts. # If not, the part is set to the highest # one the student hasn't completed, and he # can work up to the given part. (The diff --git a/macros/deprecated/compoundProblem5.pl b/macros/deprecated/compoundProblem5.pl index f1856cea1b..f3928d36d9 100644 --- a/macros/deprecated/compoundProblem5.pl +++ b/macros/deprecated/compoundProblem5.pl @@ -153,7 +153,7 @@ =head1 DESCRIPTION When you add 1 to 1 you get 2. END_SOLUTION -Normally, a solution will be tied to the section that preceeded it, +Normally, a solution will be tied to the section that preceded it, but if you want to put all your solutions at the end, for example, you can pass options the solution macros that tell it the section to attach to: diff --git a/macros/deprecated/littleneck.pl b/macros/deprecated/littleneck.pl index bbfb1fa03f..b6e7b81445 100644 --- a/macros/deprecated/littleneck.pl +++ b/macros/deprecated/littleneck.pl @@ -162,7 +162,7 @@ sub sqrt_simplify { if (($number == int($number)) && ($number > 0)) { $perfsqr = 1; #************************************************************************************* - # Check all perfect squares up to the max and store the hightest one that divides + # Check all perfect squares up to the max and store the highest one that divides #************************************************************************************* for ($i = 2; $i * $i <= $number; $i++) { $sqr = $i * $i; diff --git a/macros/deprecated/problemRandomize.pl b/macros/deprecated/problemRandomize.pl index dab263ff67..a71eab249e 100644 --- a/macros/deprecated/problemRandomize.pl +++ b/macros/deprecated/problemRandomize.pl @@ -95,7 +95,7 @@ =head1 DESCRIPTION submit saved answers (i.e., only before the due date). After the due date, the student can get new versions, but the problem will revert to the original version when they come back to the problem later. Since the default is only -to allow reseeding afer the due date, the reseeding will not be sticky by default. +to allow reseeding after the due date, the reseeding will not be sticky by default. Hardcopy ALWAYS produces the original version of the problem, regardless of the seed saved by the student. @@ -384,7 +384,7 @@ sub grader { } if (!$result->{msg}) { # hack to remove unwanted "Note: " from the problem - # (it is inserted automatically by Problem.pm when {msg} is non-emtpy). + # (it is inserted automatically by Problem.pm when {msg} is non-empty). $result->{msg} .= ''; } diff --git a/macros/deprecated/unionMacros.pl b/macros/deprecated/unionMacros.pl index 784911774d..9045bb8ef4 100644 --- a/macros/deprecated/unionMacros.pl +++ b/macros/deprecated/unionMacros.pl @@ -94,7 +94,7 @@ sub HTML { $BBR = HTML('
    '); # -# Broser-only \displaystyle +# Browser-only \displaystyle # $DISPLAY = MODES( TeX => '', @@ -127,7 +127,7 @@ sub JAVASCRIPT_PROBLEM { } # -# Modify a polynomial to remove coeficients of 1, -1 and 0 +# Modify a polynomial to remove coefficients 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 diff --git a/macros/graph/AppletObjects.pl b/macros/graph/AppletObjects.pl index e8ad53a762..be04590086 100644 --- a/macros/graph/AppletObjects.pl +++ b/macros/graph/AppletObjects.pl @@ -69,54 +69,23 @@ =head2 insertAll # Inserts both header text and object text. sub insertAll { - my $self = shift; - my %options = @_; - - my $includeAnswerBox = (defined($options{includeAnswerBox}) && $options{includeAnswerBox} == 1) ? 1 : 0; - - my $reset_button = $options{reinitialize_button} || 0; - - # Get data to be interpolated into the HTML code defined in this subroutine. - # This consists of the name of the applet and the names of the routines to get and set State - # of the applet (which is done every time the question page is refreshed and to get and set - # Config which is the initial configuration the applet is placed in when the question is - # first viewed. It is also the state which is returned to when the reset button is pressed. + my ($self, %options) = @_; # Prepare html code for storing state. my $appletName = $self->appletName; # The name of the hidden "answer" blank storing state. $self->{stateInput} = "$main::PG->{QUIZ_PREFIX}${appletName}_state"; - my $appletStateName = $self->{stateInput}; - - # Names of routines for this applet - my $getState = $self->getStateAlias; - my $setState = $self->setStateAlias; - my $getConfig = $self->getConfigAlias; - my $setConfig = $self->setConfigAlias; - my $base64_initialState = $self->base64_encode($self->initialState); # This insures that the state will be saved from one invocation to the next. - # FIXME -- with PGcore the persistant data mechanism can be used instead - main::RECORD_FORM_LABEL($appletStateName); - my $answer_value = ''; - - # Implement the sticky answer mechanism for maintaining the applet state when the question - # page is refreshed This is important for guest users for whom no permanent record of - # answers is recorded. - if (defined(${$main::inputs_ref}{$appletStateName}) && ${$main::inputs_ref}{$appletStateName} =~ /\S/) { - $answer_value = ${$main::inputs_ref}{$appletStateName}; - } elsif (defined($main::rh_sticky_answers->{$appletStateName})) { - $answer_value = shift(@{ $main::rh_sticky_answers->{$appletStateName} }); + my $answer_value = ${$main::inputs_ref}{ $self->{stateInput} } // ''; + if ($answer_value !~ /\S/ && defined(my $persistent_data = main::persistent_data($self->{stateInput}))) { + $answer_value = $persistent_data; } $answer_value =~ tr/\\$@`//d; # Make sure student answers cannot be interpolated by e.g. EV3 $answer_value =~ s/\s+/ /g; # Remove excessive whitespace from student answer # Regularize the applet's state which could be in either XML format or in XML format encoded by base64. - # In rare cases it might be simple string. Protect against that by putting xml tags around the state. - # The result: - # $base_64_encoded_answer_value -- a base64 encoded xml string - # $decoded_answer_value -- an xml string - + # In rare cases it might be a simple string. Protect against that by putting xml tags around the state. my $base_64_encoded_answer_value; my $decoded_answer_value; if ($answer_value =~ /<\??xml/i) { @@ -125,10 +94,8 @@ sub insertAll { } else { $decoded_answer_value = $self->base64_decode($answer_value); if ($decoded_answer_value =~ /<\??xml/i) { - # Great, we've decoded the answer to obtain an xml string $base_64_encoded_answer_value = $answer_value; } else { - #WTF?? apparently we don't have XML tags $answer_value = "$answer_value"; $base_64_encoded_answer_value = $self->base64_encode($answer_value); $decoded_answer_value = $answer_value; @@ -136,25 +103,33 @@ sub insertAll { } $base_64_encoded_answer_value =~ s/\r|\n//g; # Get rid of line returns + main::persistent_data($self->{stateInput} => $base_64_encoded_answer_value); + # Construct the reset button string (this is blank if the button is not to be displayed). - my $reset_button_str = $reset_button - ? qq!
    ! + my $reset_button_str = $options{reinitialize_button} + ? main::tag( + 'button', + type => 'button', + class => 'btn btn-primary applet-reset-btn mt-3', + 'data-applet-name' => $appletName, + $main::PG->maketext('Return this question to its initial state') + ) : ''; - # Combine the state_input_button and the reset button into one string. - my $state_storage_html_code = qq!! - . qq!! - . $reset_button_str; + # Construct the state storage hidden input. + my $state_storage_html_code = main::tag( + 'input', + type => 'hidden', + name => $self->{stateInput}, + id => $self->{stateInput}, + value => $base_64_encoded_answer_value + ); # Construct the answerBox (if it is requested). This is a default input box for interacting # with the applet. It is separate from maintaining state but it often contains similar # data. Additional answer boxes or buttons can be defined but they must be explicitly # connected to the applet with additional JavaScript commands. - my $answerBox_code = $includeAnswerBox - ? $answerBox_code = main::NAMED_HIDDEN_ANS_RULE($self->{answerBoxAlias}, 50) - : ''; + my $answerBox_code = $options{includeAnswerBox} ? main::NAMED_HIDDEN_ANS_RULE($self->{answerBoxAlias}, 50) : ''; # Insert header material main::HEADER_TEXT($self->insertHeader()); @@ -162,7 +137,7 @@ sub insertAll { # Return HTML or TeX strings to be included in the body of the page return main::MODES( TeX => ' {\bf ' . $self->{type} . ' applet } ', - HTML => $self->insertObject . $main::BR . $state_storage_html_code . $answerBox_code, + HTML => $self->insertObject . $state_storage_html_code . $reset_button_str . $answerBox_code, PTX => ' applet ' ); } @@ -174,7 +149,7 @@ package GeogebraWebAppletBase; sub new { my $class = shift; $class->SUPER::new( - objectText => << 'END_OBJECT_TEXT', + objectText => <<'END_OBJECT_TEXT',

    header(< - // The applet name is passed to this function, although it is not really neccessary to + // The applet name is passed to this function, although it is not really necessary to // check it, as the method will only be called for this applet. The applet name is only // provided for backwards compatibility. function myUniqueAppletOnInit(appletName) { diff --git a/macros/graph/LiveGraphicsVectorField2D.pl b/macros/graph/LiveGraphicsVectorField2D.pl index bd9e31bb98..5b29a92a04 100644 --- a/macros/graph/LiveGraphicsVectorField2D.pl +++ b/macros/graph/LiveGraphicsVectorField2D.pl @@ -83,7 +83,7 @@ =head2 VectorField2D =item C<< vectorscale => 0.2 >> -Multiplier that determines the lentgh of vectors shown in the slope field. +Multiplier that determines the length of vectors shown in the slope field. =item C<< vectorthickness => 0.001 >> diff --git a/macros/graph/LiveGraphicsVectorField3D.pl b/macros/graph/LiveGraphicsVectorField3D.pl index e284e07ac8..bd60d35cbb 100644 --- a/macros/graph/LiveGraphicsVectorField3D.pl +++ b/macros/graph/LiveGraphicsVectorField3D.pl @@ -110,7 +110,7 @@ =head2 VectorField3D =item C<< vectorscale => 0.2 >> -Multiplier that determines the lentgh of vectors shown in the slope field. +Multiplier that determines the length of vectors shown in the slope field. =item C<< vectorthickness => 0.001 >> diff --git a/macros/graph/PGanalyzeGraph.pl b/macros/graph/PGanalyzeGraph.pl index 9c63cb4a5c..73cf877397 100644 --- a/macros/graph/PGanalyzeGraph.pl +++ b/macros/graph/PGanalyzeGraph.pl @@ -72,7 +72,7 @@ sub detect_intervals { my ($left_x, $right_x); ######## - # Combine adjacent intervals with the same properites + # Combine adjacent intervals with the same properties ######## foreach my $item (@intervals) { if (defined $prev_slope) { diff --git a/macros/graph/PGgraphmacros.pl b/macros/graph/PGgraphmacros.pl index 4a17578790..81996e621c 100644 --- a/macros/graph/PGgraphmacros.pl +++ b/macros/graph/PGgraphmacros.pl @@ -384,7 +384,7 @@ =head2 insertGraph =cut -=head2 'Circle' lables +=head2 'Circle' labels Usage: diff --git a/macros/graph/PGlateximage.pl b/macros/graph/PGlateximage.pl index a8ab0900b3..c135a5751b 100644 --- a/macros/graph/PGlateximage.pl +++ b/macros/graph/PGlateximage.pl @@ -70,7 +70,7 @@ =head1 DESCRIPTION and 'pdf'. The default is an 'svg' image. You should determine if an 'svg' image works well with the LaTeX code that you utilize. If not, then use - this method to change the exension to 'png' or + this method to change the extension to 'png' or 'gif'. This macro sets the extension to 'pdf' when a diff --git a/macros/graph/PGnauGraphics.pl b/macros/graph/PGnauGraphics.pl index 728ba25950..02791dfac6 100644 --- a/macros/graph/PGnauGraphics.pl +++ b/macros/graph/PGnauGraphics.pl @@ -37,7 +37,7 @@ sub Plot { #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) +# tex_size => n - control size of tex output for pictures (default = 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 @@ -181,7 +181,7 @@ sub checkbox_table { #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) +# tex_size => n - control size of tex output for pictures (default = 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 diff --git a/macros/graph/PGstatisticsGraphMacros.pl b/macros/graph/PGstatisticsGraphMacros.pl index 71ea4ecce7..2a1622d1c2 100644 --- a/macros/graph/PGstatisticsGraphMacros.pl +++ b/macros/graph/PGstatisticsGraphMacros.pl @@ -58,7 +58,7 @@ =head2 SEE ALSO # $graph = init_statistics_graph(axes=>[0,0.0],ticks=>[10]); # $bounds = add_boxplot($graph,{"outliers"=>1}); # # or # -# $bounds = add_histogram($graph,10,1); # add a histogram with 10 bins and a multipler of 1. +# $bounds = add_histogram($graph,10,1); # add a histogram with 10 bins and a multiplier of 1. # # The multiplier is for the height of the frequencies. # # ex: if the multiplier is 2 the graph is twice as tall # diff --git a/macros/graph/PGtikz.pl b/macros/graph/PGtikz.pl index 05add89631..537e1633d9 100644 --- a/macros/graph/PGtikz.pl +++ b/macros/graph/PGtikz.pl @@ -69,7 +69,7 @@ =head1 DESCRIPTION and 'pdf'. The default is an 'svg' image. You should determine if an 'svg' image works well with the TikZ code that you utilize. If not, then use - this method to change the exension to 'png' or + this method to change the extension to 'png' or 'gif'. This macro sets the extension to 'pdf' when a diff --git a/macros/graph/StatisticalPlots.pl b/macros/graph/StatisticalPlots.pl new file mode 100644 index 0000000000..28bc916f8e --- /dev/null +++ b/macros/graph/StatisticalPlots.pl @@ -0,0 +1,901 @@ + +=head1 NAME + +StatisticalPlots.pl - A macro to create statistical plots to include in PG +problems. + +=head1 DESCRIPTION + +This macro includes a number of methods to include statistical plots in PG +problems. This is based on L which will draw using either C +(default for hardcopy) or C (default for HTML). + +The statistical plots that are available are + +=over + +=item Box Plots + +=item Bar Plots + +=item Histograms + +=item Scatter Plots + +=item Pie Charts + +=back + +=head2 USAGE + +Create a C object with + + loadMacros('StatisticalPlots.pl'); + + $statPlot = StatPlot( + xmin => -1, + xmax => 8, + ymin => -1.5, + ymax => 10, + xtick_delta => 1, + ytick_delta => 4, + aria_label => 'Bar plot of a set of data' + ); + +The C method accepts all of the same options that the C method +from the L macro accepts. + +After a C object is created then specific plots can be added to the +axes. For example, + + $statPlot->add_barplot( + [ 1 .. 6 ], + [3, 6, 7, 8, 4, 1], + fill_color => 'yellow', + stroke_width => 1, + bar_width => 0.9 + ); + +will add a bar plot to the axes. See below for more details about creating a bar +plot. + +=head1 AVAILABLE PLOTS + +The following plots are available to add to a C object. + +=head2 BAR PLOTS + +A bar plot can be added with the C method. The general form is + + $statPlot->add_barplot($xdata, $ydata, %opts); + +This adds a bar plot with bars centered at the coordinates in the array +referenced to by C<$xdata> and heights in the array referenced to by C<$ydata>. + +=head3 OPTIONS + +The following options which are specific to configuring the bar plot can be +passed in addition to any of the dataset options accepted by the +L method which draws the bars (see +L). + +=over + +=item orientation + +This can be either C<'vertical'> (default) or C<'horizontal'> to create vertical +or horizontal bars. An example with horizontal bars was given above, and the +following is an example with with vertical bars + + $statPlot->add_barplot( + [3, 6, 7, 8, 4, 1], + [ 1 .. 6 ], + orientation => 'horizontal', + fill_color => 'yellow', + stroke_width => 1, + bar_width => 0.9 + ); + +=item bar_width + +This is a number from C<0> to C<1> that specifies the width of the bars as a +fraction of the distance between the bar base coordinates (the x-coordinates for +a vertical bar plot or the y-coordinates for a horizontal bar plot). If +C<< bar_width => 1 >> (the default), then the bar width will be the distance +between the bar base coordinates (and so there is no gap between bars). In the +example above, with C<< bar_width => 0.9 >> the bar width will be C<90%> of the +distance between the bar base coordinates. + +=item stroke_color + +This sets the color of the bar borders. It is an alias for the C +L option of the +L method. See +L for more details on specifying colors. + +=item stroke_width + +This sets the width of the rectangle borders. This is an alias for the C +L. + +=item fill_colors + +This is either the name of a color palette, a reference to an array of colors, +or a reference to a hash containing the name of a color palette +(C) and number of colors to generate (C) (see L for more information). If this is a reference to an array and the +length of the array is smaller than the number of data values in the array +referenced to by C<$data>, then the colors will be cycled. + +If C is included in the list of options, this will apply the same +color to each bar. If neither C nor C is included, +then the 'rainbow' palette will be used as default. + +For example, + + fill_colors => 'rainbow' + + fill_colors => ['green', 'OliveGreen', 'DarkGreen', 'ForestGreen', 'PineGreen'] + + fill_colors => { palette_name => 'random', num_colors => 7 } + +=back + +=head2 HISTOGRAMS + +An histogram can be added to a C with the C method. The +general form is + + $statPlot->add_histogram($data, %options); + +where C<$data> is a reference to an array of univariate data. The C<%options> +parameter includes options for the histogram such as the number of bins as well +as options for drawing the bars. + +The following example generates a data set using the L +function which produces a random set of normally distributed data values, and +constructs a histogram for that data set. + + loadMacros('StatisticalPlots.pl', 'PGstatisticsmacros.pl'); + + $statPlot = StatPlot( + xmin => 0, + xmax => 65, + ymin => 0, + ymax => 12, + xtick_delta => 10, + ytick_delta => 2 + ); + $statPlot->add_histogram( + [ urand(30, 9, 50, 6) ], + min => 10, + max => 60, + bins => 10, + fill_color => 'LightGreen', + width => 1 + ); + +=head3 Options + +The following options which are specific to configuring the histogram can be +passed in addition to any of the dataset options accepted by the +L method which draws the bars for the +histogram (see L). Note that if the +C option is set, then C<< fill => 'self' >> is automatically set, +and so you do not need to specify that option. + +=over + +=item orientation + +This can be either C<'vertical'> (default) or C<'horizontal'> to create vertical +or horizontal bars. + +=item min + +The left edge of the leftmost bar. If not defined, the minimum value in the +C<$data> array is used. + +=item max + +The right edge of the rightmost bar. If not defined, the maximum value in +C<$data> is used. + +=item bins + +The number of bins/bars to use for the histogram. This must be an integer +greater than 0. The default value is 10. + +=item normalize + +If this is set to 0 (the default), then the height of each bar is the number of +data values in the bin. If this is 1, then the heights are scaled so the sum of +the heights of the bars is 1. + +=item stroke_color + +This sets the color of the bar borders. It is an alias for the C +L option of the +L method. See +L for more details on specifying colors. + +=item stroke_width + +This sets the width of the rectangle borders. This is an alias for the C +L. + +=back + +=head2 BOX PLOTS + +A box plot (also called a box and whiskers plot) can be created with the +C method. The general form for adding a single box plot is + + $statPlot->add_boxplot($data, %options); + +or to add multiple box plots is + + $statPlot->add_boxplot([$data1, $data2, ...], %options); + +where C<$data> (or C<$data1>, C<$data2>, ...) is a reference to an array of +univariate data or a reference to a hash of the box plot characteristics. Box +plots are created using the five number summary (minimum, first quartile, +median, third quartile, maximum) of the data which is calculated using the +L method. + +An example of creating a box plot with a reference to an array of univariate +data is + + $statPlot = StatPlot( + xmin => 0, + xmax => 200, + ymin => -5, + ymax => 25, + xtick_delta => 25, + yvisible => 0, + show_grid => 0, + aspect_ratio => 4, + rounded_corners => 1 + ); + + $statPlot->add_boxplot( + [ urand(100, 25, 75, 6) ], + fill_color => 'LightBlue', + stroke_width => 1 + ); + +If C<$data> is a hash reference, then it must contains the fields C that form the five number summary. Optionally, the field +C may also be included which is a reference to an array of values +which will be plotted outside of the whiskers. For example, + + $boxplot = StatPlot( + xmin => 100, + xmax => 400, + ymin => -5, + ymax => 25, + xtick_delta => 50, + yvisible => 0, + show_grid => 0, + aspect_ratio => 4 + ); + + $boxplot->add_boxplot({ + min => random(150, 175, 5), + q1 => random(180, 225, 5), + median => random(250, 275, 5), + q3 => random(280, 320, 10), + max => random(325, 350, 5), + outliers => [115, 130] + }); + +=head3 Options + +The following options which are specific to configuring the box plot can be +passed in addition to any of the L. +Note that if the C option is set, then C<< fill => 'self' >> is +automatically set, and so you do not need to specify that option. + +=over + +=item orientation + +This is the direction of the box plot and can be either 'horizontal' (the +default) or 'vertical'. + +=item box_center + +The location of the center of the box. This is optional and if not defined the +box plot will be centered in the range of the minor axis (the y-axis for +horizontal box plots and the x-axis for vertical box plots). + +If multiple box plots are being added and this option is not set, then the box +plots will be equally spaced in the range of the minor axis. + +If included, this option must be a reference to an array of values (in the +x-direction for vertical plots and y-direction for horizontal plots) at which to +center the added box plots. The number of entries in the array must be the same +as the number of box plots that are being added. For example, if three box plots +are being added this could be + + box_center => [3, 6, 9] + +=item box_width + +The width of the box in the direction perpendicular to the orientation. The +default is to use half of the range of the minor axis. + +If multiple box plots are defined, this should only be a single value. + +=item whisker_cap + +This should be set to 0 (default) or 1. If this value is 1, lines that are +perpendicular to the whiskers will be added at the ends of the whiskers of the +box plot. The length of the lines are determined by the C option. + +=item cap_width + +The width of the cap as a fraction of the box width. Default value is 0.2. + +=item outlier_mark + +The shape of the mark to use for outliers. The default is 'plus'. See the +L for other mark options. + +=item stroke_color + +This sets the color of the boundary of the box and the whiskers. It is an alias +for the C L. See +L for more details on specifying colors. + +=item stroke_width + +This sets the width of the boundary of the rectangle and the whiskers. This is +an alias for the C L. + +=back + +=head2 SCATTER PLOTS + +A scatter plot can be created with the C method. The general +form for adding a scatter plot is + + $statPlot->add_scatterplot($data, %options); + +where C<$data> is a reference to an array of array references of C, C +pairs. For example, + + $statPlot = StatPlot( + xmin => -1, + xmax => 15, + ymin => -1, + ymax => 15, + xtick_delta => 5, + ytick_delta => 5, + ); + + $statPlot->add_scatterplot( + [ [1, 1], [2, 3], [3, 4], [5, 5], [7, 8], [10, 9], [12, 10] ], + marks => 'diamond', + mark_size => 5, + color => 'orange' + ); + +This method simply calls the L method with the +following L set. Any of these can be +changed or any of the other data set options set in the C<%options> parameter. + +=over + +=item linestyle + +This sets the style of the lines between the marks is set to 'none' by this +method, so that lines are not drawn between the points. + +=item marks + +This sets the symbol to use for the marks and is set to C<'circle'> by this +method. See the L for other mark +options. + +=item mark_size + +This sets the size of the marks and is set to 3 by this method. + +=item mark_color + +This sets the color of the marks and is an alias for the C +L option. See L +for more details on specifying colors. + +=back + +If more than one dataset is to be plotted, then call the C +method multiple times. This can be done with a single C method +call, but this wrapper is convenient when the default options above are desired. + +=head2 PIE CHARTS + +A pie chart is a circle that divided into sectors whose size is proportional to +an input array. The sectors are each assigned a color and a label. This method +will also produce donut charts (or ring charts), which is a pie chart with a +hole. + +The general form for calling the C method is + + $statPlot->add_piechart($data, %options); + +where C<$data> is a reference to an array of values. + +=head3 Options + +The following options which are specific to configuring the pie chart can be +passed in addition to any of the L. + +=over + +=item center + +The center of the circle as an array reference. The default value is C<[0, 0]>. + +=item radius + +The radius of the circle. The default value of 4 is chosen to fit nicely with +the default values for the bounding box of the C which ranges from -5 +to 5 in both the x and y directions. + +=item inner_radius + +This is the radius of the inner circle of the chart. Set this to a value less +than the C for a donut or ring chart. The default value is 0. + +=item angle_offset + +This is the angle in degrees from the positive horizontal axis at which the +first sector begins. This is 0 by default. + +=item fill_colors + +This is either the name of a color palette, a reference to an array of colors, +or a reference to a hash containing the name of a color palette +(C) and number of colors to generate (C) (see L for more information). If this is a reference to an array and the +length of the array is smaller than the number of data values in the array +referenced to by C<$data>, then the colors will be cycled. The default is to use +the C<'rainbow'> color palette. + +For example, + + fill_colors => 'rainbow' + + fill_colors => ['green', 'OliveGreen', 'DarkGreen', 'ForestGreen', 'PineGreen'] + + fill_colors => { palette_name => 'random', num_colors => 7 } + +=item color_sectors + +If this is 1 (the default), then colors are used for the pie chart. If 0, then +the sectors are not filled. See L for details on selecting +colors. + +=item sector_labels + +The labels for the sector as a array reference of strings or values. The +default is for no labels. If this is used, the length of this must be the same +as the C<$data> array reference. + +=back + +=head2 COLOR PALETTES + +The color palettes for the bar plots and pie charts can be selected with the +C function. This allows a number of built-in or generated color +palettes. To get an array reference of either named or generated colors call + + color_palette($name, $numColors); + +For example, + + color_palette('rainbow'); + +returns the 6 colors of the rainbow. Some of the palettes have fixed numbers of +colors, whereas others have variable numbers. If C<$numColors> is not defined, +then some palettes return a fixed number (like 'rainbow') and if the +C<$numColors> is needed, then the default of 10 is assumed. + +=head3 PALETTE NAMES + +=over + +=item rainbow + +The 6 colors of the rainbow from violet to red. The C<$numColors> value is +ignored for this palette. + +=item reds + +This will return a selection of red colors. The C<$numColors> value is ignored +for this palette. + +=item blues + +This will return a selection of blue colors. The C<$numColors> value is ignored +for this palette. + +=item greens + +This will return a selection of green colors. The C<$numColors> value is ignored +for this palette. + +=item random + +This will return C<$numColors> random colors from the defined SVG colors. + +=back + +=cut + +BEGIN { strict->import; } + +sub _StatisticalPlots_init { + main::PG_restricted_eval('sub StatPlot { Plots::StatPlot->new(@_); }'); +} + +loadMacros('PGstatisticsmacros.pl', 'PGauxiliaryFunctions.pl'); + +package Plots::StatPlot; +our @ISA = qw(Plots::Plot); + +sub add_histogram { + my ($self, $data, %options) = @_; + + $options{orientation} //= 'vertical'; + + my $numBins = delete $options{bins} // 10; + $numBins = 10 unless $numBins =~ /^\d+$/ && $numBins > 0; + + my $min = delete $options{min} // main::min(@$data); + my $max = delete $options{max} // main::max(@$data); + my $binWidth = ($max - $min) / $numBins; + + my @frequencies = (0) x $numBins; + ++$frequencies[ int(($_ - $min) / $binWidth) ] for @$data; + + if (delete $options{normalize}) { + my $total = 0; + $total += $_ for @frequencies; + @frequencies = map { $_ / $total } @frequencies; + } + + my @xdata = map { $min + (0.5 + $_) * $binWidth } (0 .. $#frequencies); + + return $self->add_barplot( + $options{orientation} eq 'vertical' ? (\@xdata, \@frequencies) : (\@frequencies, \@xdata), %options + ), + \@frequencies; +} + +sub add_barplot { + my ($self, $xdata, $ydata, %options) = @_; + + $options{bar_width} //= 1; + $options{orientation} //= 'vertical'; + set_plot_option_aliases(\%options); + + Value::Error('The first two arguments must be references to arrays of the same length.') + unless (ref $xdata eq 'ARRAY' && ref $xdata eq 'ARRAY' && @$xdata == @$ydata); + + # Assume that the values in $xdata are equally spaced. + # TODO: Should arbitrarily spaced bars be handled? + my $bar_width = $options{orientation} eq 'vertical' ? $xdata->[1] - $xdata->[0] : $ydata->[1] - $ydata->[0]; + + my $fill_colors = + ref $options{fill_colors} eq 'HASH' + ? color_palette($options{fill_colors}{palette_name}, $options{fill_colors}{num_colors}) + : (!defined $options{fill_colors} || ref $options{fill_colors} ne 'ARRAY') + ? color_palette($options{fill_colors}) + : $options{fill_colors}; + + return $self->add_rectangle( + map { [ + $options{orientation} eq 'vertical' + ? ( + [ $xdata->[$_] - 0.5 * $bar_width * $options{bar_width}, 0 ], + [ $xdata->[$_] + 0.5 * $bar_width * $options{bar_width}, $ydata->[$_] ] + ) + : ( + [ 0, $ydata->[$_] - 0.5 * $bar_width * $options{bar_width} ], + [ $xdata->[$_], $ydata->[$_] + 0.5 * $bar_width * $options{bar_width} ] + ), + fill_color => $fill_colors->[ $_ % @$fill_colors ], + %options + ] } 0 .. $#$xdata + ); +} + +sub add_boxplot { + my ($self, $data, %options) = @_; + + $options{orientation} //= 'horizontal'; + $options{whisker_cap} //= 0; + $options{cap_width} //= 0.2; + $options{outlier_mark} //= 'plus'; + set_plot_option_aliases(\%options); + + if (ref $data eq 'ARRAY' && (ref $data->[0] eq 'ARRAY' || ref $data->[0] eq 'HASH')) { + my $box_centers; + if ($options{box_center}) { + Value::Error(q{The "box_center" option must be a reference to an array } + . 'with the same length as the number of box plots being added.') + unless (ref $options{box_center} eq 'ARRAY' && @{ $options{box_center} } == @$data); + $box_centers = delete $options{box_center}; + } else { + unless ($options{box_width}) { + $options{box_width} = + ($options{orientation} eq 'vertical' ? $self->axes->xaxis('max') : $self->axes->yaxis('max')) / + (2.5 * @$data); + } + $box_centers = [ map { 2 * $options{box_width} * $_ } (1 .. @$data + 1) ]; + } + for (0 .. $#$data) { + $options{box_center} = $box_centers->[$_]; + $self->_add_boxplot($data->[$_], %options); + } + } else { + $self->_add_boxplot($data, %options); + } +} + +sub _add_boxplot { + my ($self, $data, %options) = @_; + + my $orientation = $options{orientation} // 'horizontal'; + my $params; + if (ref $data eq 'ARRAY') { + my @five_point = main::five_point_summary(@$data); + $params = { + min => $five_point[0], + q1 => $five_point[1], + median => $five_point[2], + q3 => $five_point[3], + max => $five_point[4] + }; + } elsif (ref $data eq 'HASH') { + # Check that all elements of the five number summary were provided. + my %missing; + for ('min', 'q1', 'median', 'q3', 'max') { + $missing{$_} = 1 unless defined $data->{$_}; + } + for (keys %missing) { + Value::Error(qq{The parameter "$_" is missing from the box plot five number summary.}); + } + + $params = $data; + } + + if ($orientation eq 'horizontal') { + my $box_center = $options{box_center} // 0.5 * $self->axes->yaxis->{max}; + my $box_width = $options{box_width} // 0.5 * $self->axes->yaxis->{max}; + + $self->add_rectangle([ $params->{q1}, $box_center - 0.5 * $box_width ], + [ $params->{q3}, $box_center + 0.5 * $box_width ], %options); + $self->add_dataset( + [ [ $params->{min}, $box_center ], [ $params->{q1}, $box_center ], %options ], + [ [ $params->{q3}, $box_center ], [ $params->{max}, $box_center ], %options ], + [ + [ $params->{median}, $box_center - 0.5 * $box_width ], + [ $params->{median}, $box_center + 0.5 * $box_width ], + %options + ] + ); + + if ($options{whisker_cap}) { + $self->add_dataset( + [ + [ $params->{max}, $box_center - 0.5 * $options{cap_width} * $box_width ], + [ $params->{max}, $box_center + 0.5 * $options{cap_width} * $box_width ], + %options + ], + [ + [ $params->{min}, $box_center - 0.5 * $options{cap_width} * $box_width ], + [ $params->{min}, $box_center + 0.5 * $options{cap_width} * $box_width ], + %options + ] + ); + } + + if (ref $params->{outliers} eq 'ARRAY') { + $self->add_dataset( + (map { [ $_, $box_center ] } @{ $params->{outliers} }), + linestyle => 'none', + marks => $options{outlier_mark}, + marksize => 3 + ); + } + } elsif ($orientation eq 'vertical') { + my $box_center = $options{box_center} // 0.5 * $self->axes->xaxis->{max}; + my $box_width = $options{box_width} // 0.5 * $self->axes->xaxis->{max}; + + $self->add_rectangle([ $box_center - 0.5 * $box_width, $params->{q1} ], + [ $box_center + 0.5 * $box_width, $params->{q3} ], %options); + $self->add_dataset( + [ [ $box_center, $params->{min} ], [ $box_center, $params->{q1} ], %options ], + [ [ $box_center, $params->{q3} ], [ $box_center, $params->{max}, ], %options ], + [ + [ $box_center - 0.5 * $box_width, $params->{median} ], + [ $box_center + 0.5 * $box_width, $params->{median} ], + %options + ] + ); + + if ($options{whisker_cap}) { + $self->add_dataset( + [ + [ $box_center - 0.5 * $options{cap_width} * $box_width, $params->{max} ], + [ $box_center + 0.5 * $options{cap_width} * $box_width, $params->{max}, ], + %options + ], + [ + [ $box_center - 0.5 * $options{cap_width} * $box_width, $params->{min} ], + [ $box_center + 0.5 * $options{cap_width} * $box_width, $params->{min} ], + %options + ] + ); + } + + if (ref $params->{outliers} eq 'ARRAY') { + $self->add_dataset( + (map { [ $box_center, $_ ] } @{ $params->{outliers} }), + linestyle => 'none', + marks => $options{outlier_mark}, + marksize => 3 + ); + } + } +} + +sub add_scatterplot { + my ($self, $data, %options) = @_; + + $options{linestyle} //= 'none'; + $options{marks} //= 'circle'; + $options{mark_size} //= 3; + set_plot_option_aliases(\%options); + + $self->add_dataset(@$data, %options); + +} + +sub add_piechart { + my ($self, $data, %options) = @_; + + $options{center} //= [ 0, 0 ]; + $options{radius} //= 4; + $options{angle_offset} //= 0; + $options{inner_radius} //= 0; + set_plot_option_aliases(\%options); + + Value::Error('The number of labels must equal the number of sectors in the pie chart') + unless (defined $options{labels} && @$data == @{ $options{labels} }); + + my $fill_colors = + ref $options{fill_colors} eq 'HASH' + ? color_palette($options{fill_colors}{palette_name}, $options{fill_colors}{num_colors}) + : (!defined $options{fill_colors} || ref $options{fill_colors} ne 'ARRAY') + ? color_palette($options{fill_colors}) + : $options{fill_colors}; + + my $pi = 4 * atan2(1, 1); + my $total = 0; + $total += $_ for @$data; + + my $theta = $options{angle_offset} * $pi / 180; # first angle of the sector + for (0 .. $#$data) { + my $delta_theta = 2 * $pi * $data->[$_] / $total; + $self->add_multipath( + [ + [ + "$options{center}[0] + $options{radius} * cos(t)", + "$options{center}[1] + $options{radius} * sin(t)", + $theta, + $theta + $delta_theta + ], + [ + "$options{center}[0] + $options{inner_radius} * cos(t)", + "$options{center}[1] + $options{inner_radius} * sin(t)", + $theta + $delta_theta, + $theta + ], + ], + 't', + cycle => 1, + fill => 'self', + fill_color => $fill_colors->[ $_ % @$fill_colors ], + %options + ); + # add the labels if defined + if ($options{labels}) { + my $alpha = $theta + 0.5 * $delta_theta; + + $self->add_label( + $options{radius} * cos($alpha), $options{radius} * sin($alpha), $options{labels}[$_], + anchor => 180 * (1 + $alpha / $pi), + padding => 15 + ); + } + $theta += $delta_theta; + } + +} + +sub set_plot_option_aliases { + my $options = shift; + my %aliases = (stroke_width => 'width', stroke_color => 'color', mark_color => 'color'); + for (keys %aliases) { + $options->{ $aliases{$_} } = delete $options->{$_} if defined $options->{$_}; + } + return %$options; +} + +sub color_palette { + my ($palette_name, $num_colors) = @_; + + $palette_name //= 'rainbow'; + + if ($palette_name eq 'rainbow') { + return [ 'Violet', 'blue', 'green', 'yellow', 'orange', 'red' ]; + } elsif ($palette_name eq 'greens') { + return [ 'Green', 'Olive', 'DarkGreen', 'LawnGreen', 'MediumAquamarine', 'LimeGreen' ]; + } elsif ($palette_name eq 'blues') { + return [ 'Blue', 'MidnightBlue', 'MediumBlue', 'LightSkyBlue', 'DodgerBlue', 'DarkBlue', 'CornflowerBlue' ]; + } elsif ($palette_name eq 'reds') { + return [ 'Red', 'Crimson', 'DarkRed', 'FireBrick', 'IndianRed', 'Maroon', 'Tomato' ]; + } elsif ($palette_name eq 'random') { + return [ + main::random_subset( + $num_colors // 10, + ( + 'AliceBlue', 'AntiqueWhite', 'Aqua', 'Aquamarine', + 'Azure', 'Beige', 'Bisque', 'Black', + 'BlanchedAlmond', 'Blue', 'BlueViolet', 'Brown', + 'BurlyWood', 'CadetBlue', 'Chartreuse', 'Chocolate', + 'Coral', 'CornflowerBlue', 'Cornsilk', 'Crimson', + 'Cyan', 'DarkBlue', 'DarkCyan', 'DarkGoldenrod', + 'DarkGray', 'DarkGreen', 'DarkGrey', 'DarkKhaki', + 'DarkMagenta', 'DarkOliveGreen', 'DarkOrange', 'DarkOrchid', + 'DarkRed', 'DarkSalmon', 'DarkSeaGreen', 'DarkSlateBlue', + 'DarkSlateGray', 'DarkSlateGrey', 'DarkTurquoise', 'DarkViolet', + 'DeepPink', 'DeepSkyBlue', 'DimGray', 'DimGrey', + 'DodgerBlue', 'FireBrick', 'FloralWhite', 'ForestGreen', + 'Fuchsia', 'Gainsboro', 'GhostWhite', 'Gold', + 'Goldenrod', 'Gray', 'Green', 'GreenYellow', + 'Grey', 'Honeydew', 'HotPink', 'IndianRed', + 'Indigo', 'Ivory', 'Khaki', 'Lavender', + 'LavenderBlush', 'LawnGreen', 'LemonChiffon', 'LightBlue', + 'LightCoral', 'LightCyan', 'LightGoldenrodYellow', 'LightGray', + 'LightGreen', 'LightGrey', 'LightPink', 'LightSalmon', + 'LightSeaGreen', 'LightSkyBlue', 'LightSlateGray', 'LightSlateGrey', + 'LightSteelBlue', 'LightYellow', 'Lime', 'LimeGreen', + 'Linen', 'Magenta', 'Maroon', 'MediumAquamarine', + 'MediumBlue', 'MediumOrchid', 'MediumPurple', 'MediumSeaGreen', + 'MediumSlateBlue', 'MediumSpringGreen', 'MediumTurquoise', 'MediumVioletRed', + 'MidnightBlue', 'MintCream', 'MistyRose', 'Moccasin', + 'NavajoWhite', 'Navy', 'OldLace', 'Olive', + 'OliveDrab', 'Orange', 'OrangeRed', 'Orchid', + 'PaleGoldenrod', 'PaleGreen', 'PaleTurquoise', 'PaleVioletRed', + 'PapayaWhip', 'PeachPuff', 'Peru', 'Pink', + 'Plum', 'PowderBlue', 'Purple', 'RebeccaPurple', + 'Red', 'RosyBrown', 'RoyalBlue', 'SaddleBrown', + 'Salmon', 'SandyBrown', 'SeaGreen', 'Seashell', + 'Sienna', 'Silver', 'SkyBlue', 'SlateBlue', + 'SlateGray', 'SlateGrey', 'Snow', 'SpringGreen', + 'SteelBlue', 'Tan', 'Teal', 'Thistle', + 'Tomato', 'Turquoise', 'Violet', 'Wheat', + 'White', 'WhiteSmoke', 'Yellow', 'YellowGreen' + ) + ) + ]; + } +} + +1; diff --git a/macros/graph/parserGraphTool.pl b/macros/graph/parserGraphTool.pl index 0b05d1e3ec..73d052d738 100644 --- a/macros/graph/parserGraphTool.pl +++ b/macros/graph/parserGraphTool.pl @@ -45,7 +45,7 @@ =head1 GRAPH OBJECTS lines (GraphTool::GraphObject::Line) circles (GraphTool::GraphObject::Circle) parabolas (GraphTool::GraphObject::Parabola) - quadratics (GraphTool::GraphObject::Qudratic) + quadratics (GraphTool::GraphObject::Quadratic) cubics (GraphTool::GraphObject::Cubic) intervals (GraphTool::GraphObject::Interval) sine waves (GraphTool::GraphObject::SineWave) @@ -331,8 +331,12 @@ =head1 OPTIONS =item scaleX, scaleY (Default: C<< scaleX => 1, scaleY => 1 >>) -These are the scale of the ticks on the x and y axes. That is the distance between two -successive ticks on the axis (including both major and minor ticks). +These are the scale of the tick distances on the x and y axes. That is the distance between two +successive major ticks on the axis will be the product of the ticks distance and the scale. +This is usually used in conjunction with the C and C options. For +example, if C is 2, C is 3, and C is 'a', then the first +positive major x axis tick will occur 6 units to the right and be labeled '2a', and the next +major x axis tick will occur 12 units to the right and be labeled '4a'. =item scaleSymbolX, scaleSymbolY (Default: C<< scaleSymbolX => '', scaleSymbolY => '' >>) @@ -1164,7 +1168,7 @@ sub generateHTMLAnswerGraph { $html . ($options{longDescription} =~ s/LONG-DESCRIPTION-ID/${ans_name}_${idSuffix}_details/r) ) if $options{longDescription}; - return $html . main::tag('script', <<~ "END_SCRIPT"); + return $html . main::tag('script', <<~"END_SCRIPT"); (() => { const initialize = () => { graphTool('${ans_name}_$idSuffix', { @@ -1195,6 +1199,47 @@ sub generateHTMLAnswerGraph { END_SCRIPT } +# This is essentially copied from contextFraction.pl. +sub continuedFraction { + my ($x) = @_; + + my $step = $x; + my $n = int($step); + my ($h0, $h1, $k0, $k1) = (1, $n, 0, 1); + + while ($step != $n) { + $step = 1 / ($step - $n); + $n = int($step); + my ($newh, $newk) = ($n * $h1 + $h0, $n * $k1 + $k0); + last if $newk > 10**8; # Bail if the denominator is skyrocketing out of control. + ($h0, $h1, $k0, $k1) = ($h1, $newh, $k1, $newk); + } + + return ($h1, $k1); +} + +sub formatTickLabelText { + my ($self, $value, $axis) = @_; + my $coordinateHintsType = $self->{"coordinateHintsType$axis"} // $self->{coordinateHintsType}; + if ($coordinateHintsType eq 'fraction' || $coordinateHintsType eq 'mixed') { + my ($num, $den) = continuedFraction(abs($value)); + if ($num && $den != 1 && !($num == 1 && $den == 1)) { + if ($coordinateHintsType eq 'fraction' || $num < $den) { + $value = ($value < 0 ? '-' : '') . "\\frac{$num}{$den}"; + } else { + my $int = int($num / $den); + my $properNum = $num % $den; + $value = ($value < 0 ? '-' : '') . "$int\\frac{$properNum}{$den}"; + } + } + } + my $scaleSymbol = $self->{"scaleSymbol$axis"} // ''; + return + $value eq '0' ? '0' + : $scaleSymbol ? ($value eq '1' ? $scaleSymbol : $value eq '-1' ? "-$scaleSymbol" : "$value$scaleSymbol") + : $value; +} + sub generateTeXGraph { my ($self, %options) = @_; @@ -1203,7 +1248,7 @@ sub generateTeXGraph { return &{ $self->{printGraph} } if ref($self->{printGraph}) eq 'CODE'; - my @size = $self->{numberLine} ? (500, 100) : (500, 500); + my @size = $self->{numberLine} ? (500, 110) : (500, 500); my $graph = main::createTikZImage(); $graph->tikzLibraries('arrows.meta'); @@ -1264,32 +1309,71 @@ sub generateTeXGraph { } # Horizontal axis ticks and labels - my @xTicks = grep { $_ < $self->{bBox}[2] } - map { $_ * $self->{ticksDistanceX} } (1 .. $self->{bBox}[2] / $self->{ticksDistanceX}); - push(@xTicks, + my @xTicks = grep { $_ > $self->{bBox}[0] } - map { -$_ * $self->{ticksDistanceX} } (1 .. -$self->{bBox}[0] / $self->{ticksDistanceX})); + map { -$_ * $self->{ticksDistanceX} * $self->{scaleX} } + reverse(1 .. -$self->{bBox}[0] / ($self->{ticksDistanceX} * $self->{scaleX})); + my $numNegative = @xTicks; # Add zero if this is a number line and 0 is in the given range. - push(@xTicks, 0) if ($self->{numberLine} && $self->{bBox}[2] > 0 && $self->{bBox}[0] < 0); + push(@xTicks, 0) if $self->{numberLine} && $self->{bBox}[2] > 0 && $self->{bBox}[0] < 0; + push(@xTicks, + grep { $_ < $self->{bBox}[2] } + map { $_ * $self->{ticksDistanceX} * $self->{scaleX} } + (1 .. $self->{bBox}[2] / ($self->{ticksDistanceX} * $self->{scaleX}))); my $tickSize = $self->{numberLine} ? '9' : '5'; $tikz .= - "\\foreach \\x in {" - . join(',', @xTicks) - . "}{\\draw[thin] (\\x,${tickSize}pt) -- (\\x,-${tickSize}pt) node[below]{\\(\\x\\)};}\n" + "\\foreach \\x/\\label in {" + . join(',', map { "$_/" . $self->formatTickLabelText($_ / $self->{scaleX}, 'X') } @xTicks) + . "}{\\draw[thin, opacity = 0.5] (\\x,${tickSize}pt) -- (\\x,-${tickSize}pt) " + . "node[baseline, yshift = -15pt, opacity = 1]{\\(\\label\\)};}\n" if (@xTicks); + # Add horizontal axis minor ticks. + splice(@xTicks, $numNegative, 0, 0) if !$self->{numberLine} || ($self->{bBox}[0] <= 0 && $self->{bBox}[2] >= 0); + unshift(@xTicks, $xTicks[0] - $self->{ticksDistanceX} * $self->{scaleX}) if $self->{bBox}[0] < 0; + push(@xTicks, $xTicks[-1] + $self->{ticksDistanceX} * $self->{scaleX}) if $self->{bBox}[2] > 0; + my @xMinorTicks; + my $xMinorTickDelta = $self->{ticksDistanceX} * $self->{scaleX} / ($self->{minorTicksX} + 1); + for my $tickIndex (0 .. $#xTicks - 1) { + push(@xMinorTicks, map { $xTicks[$tickIndex] + $_ * $xMinorTickDelta } 1 .. $self->{minorTicksX}); + } + $tikz .= + "\\foreach \\x in {" + . join(',', @xMinorTicks) + . "}{\\draw[thin, opacity = 0.5] (\\x,0) -- (\\x,-${tickSize}pt);}\n" + if (@xMinorTicks); + # Vertical axis ticks and labels unless ($self->{numberLine}) { - my @yTicks = grep { $_ < $self->{bBox}[1] } - map { $_ * $self->{ticksDistanceY} } (1 .. $self->{bBox}[1] / $self->{ticksDistanceY}); - push(@yTicks, + my @yTicks = grep { $_ > $self->{bBox}[3] } - map { -$_ * $self->{ticksDistanceY} } (1 .. -$self->{bBox}[3] / $self->{ticksDistanceY})); + map { -$_ * $self->{ticksDistanceY} * $self->{scaleY} } + reverse(1 .. -$self->{bBox}[3] / ($self->{ticksDistanceY} * $self->{scaleY})); + my $numNegative = @yTicks; + push(@yTicks, + grep { $_ < $self->{bBox}[1] } + map { $_ * $self->{ticksDistanceY} * $self->{scaleY} } + (1 .. $self->{bBox}[1] / ($self->{ticksDistanceY} * $self->{scaleY}))); $tikz .= - "\\foreach \\y in {" - . join(',', @yTicks) - . "}{\\draw[thin] (5pt,\\y) -- (-5pt,\\y) node[left]{\$\\y\$};}\n" + "\\foreach \\y/\\label in {" + . join(',', map { "$_/" . $self->formatTickLabelText($_ / $self->{scaleY}, 'Y') } @yTicks) + . "}{\\draw[thin, opacity = 0.5] (5pt,\\y) -- (-5pt,\\y) node[left, opacity = 1]{\$\\label\$};}\n" if (@yTicks); + + # Add vertical axis minor ticks. + splice(@yTicks, $numNegative, 0, 0); + unshift(@yTicks, $yTicks[0] - $self->{ticksDistanceY} * $self->{scaleY}) if $self->{bBox}[3] < 0; + push(@yTicks, $yTicks[-1] + $self->{ticksDistanceY} * $self->{scaleY}) if $self->{bBox}[1] > 0; + my @yMinorTicks; + my $yMinorTickDelta = $self->{ticksDistanceY} * $self->{scaleY} / ($self->{minorTicksY} + 1); + for my $tickIndex (0 .. $#yTicks - 1) { + push(@yMinorTicks, map { $yTicks[$tickIndex] + $_ * $yMinorTickDelta } 1 .. $self->{minorTicksY}); + } + $tikz .= + "\\foreach \\y in {" + . join(',', @yMinorTicks) + . "}{\\draw[thin, opacity = 0.5] (0, \\y) -- (-5pt, \\y);}\n" + if @yMinorTicks; } # Border box @@ -1724,7 +1808,7 @@ sub new { $self->{isLine} = abs($self->{a}) < 0.000001; if ($self->{isLine}) { - # Colinear points + # Collinear points $self->{a} = 1; $self->{function} = sub { return ($y2 - $y1) / ($x2 - $x1) * ($_[0] - $x1) + $y1; }; } else { @@ -1840,7 +1924,7 @@ sub new { $self->{degree} = abs($self->{c3}) < 0.000001 && abs($c2) < 0.000001 ? 1 : abs($self->{c3}) < 0.000001 ? 2 : 3; if ($self->{degree} == 1) { - # Colinear points + # Collinear points $self->{c3} = 1; $self->{function} = sub { return ($y2 - $y1) / ($x2 - $x1) * ($_[0] - $x1) + $y1; }; } elsif ($self->{degree} == 2) { diff --git a/macros/graph/plotly3D.pl b/macros/graph/plotly3D.pl index 6a13fbe0e0..3af375c6e8 100644 --- a/macros/graph/plotly3D.pl +++ b/macros/graph/plotly3D.pl @@ -300,7 +300,7 @@ =head1 Graph3D OPTIONS Add a JavaScript scene configuration dictionary to the plotly layout. This can be used to configure various aspects of the plot, such as the aspect ratio, and view range of the 3D axes. The scene is a string -which contains a JavaScript dictonary to pass to plotly. Example: +which contains a JavaScript dictionary to pass to plotly. Example: scene => 'aspectmode: "manual",' . 'aspectratio: {x: 1, y: 1, z: 1},' @@ -374,7 +374,7 @@ sub TeX { if ($self->{image}) { $out .= &main::image($self->{image}, tex_size => 950); } else { - $out .= '3D image not avaialble. You must view it online.'; + $out .= '3D image not available. You must view it online.'; } $out .= "\n\\end{minipage}}\n"; @@ -492,7 +492,7 @@ sub genPoints { } elsif ($type eq 'perl') { $self->buildArray; } else { - Value::Error("Unkown plot type: $type\n"); + Value::Error("Unknown plot type: $type\n"); } } diff --git a/macros/graph/plots.pl b/macros/graph/plots.pl index 64ad60e51a..7fa04ab0f1 100644 --- a/macros/graph/plots.pl +++ b/macros/graph/plots.pl @@ -5,18 +5,18 @@ =head1 NAME =head1 DESCRIPTION -This macro creates a Plots object that is used to add data of different +This macro creates a Plot object that is used to add data of different elements of a 2D plot, then draw the plot. The plots can be drawn using different -formats. Currently C (using PGFplots), C, and the legacy C -graphics format are available. Default is to use C for HTML output and -C for hardcopy. +formats. Currently C (using PGFplots) and C graphics format are +available. The default is to use C for HTML output and C for +hardcopy. Note, due to differences in features between C and C, not all options work with both. =head1 USAGE -First create a Plots object: +First create a Plot object: loadMacros('plots.pl'); $plot = Plot( @@ -36,6 +36,54 @@ =head1 USAGE Options that start with C configure the xaxis, options that start with C configure the yaxis, and all other options are Axes styles. +In addition to the options for configuring the L, the +following options may be passed. + +=over + +=item width + +The width of the image. The default value of this option is the +C value in the environment, or C<350> if that is not set or +is C<0>. + +=item height + +The height of the image. The default value of this option is the C. If +this is explicitly set to a positive integer value, then that height will be +used. If this is C, then the height of the image will be automatically +determined. If the C style setting for the C object +is C<1>, then the height will be computed to maintain the aspect ratio of the +image. Otherwise it will be set to value of the C option. + +=item tex_size + +The size of the image in hardcopy. See L +for more details on this setting. + +=item rounded_corners + +Determines if the image will be displayed in a rectangle with rounded corners +or sharp corners. The default value for this option is C<0> which means that +sharp corners will be used. If this is set to C<1>, then rounded corners will +be used. Note that this may not work well for images that have elements of the +plot near or in the corners. + +=item texPackages + +This is an array reference of additional TeX packages to use when generating the +TikZ output. By default this includes "amsmath" and "pgfplots". This is useful +for adding packages for labels such as "cancel". See L for details +on loading packages and options. + +=item tikzLibraries + +This is an array reference of additional TikZ libraries to include when generating +the TikZ output. By default this includes "arrows.meta", "plotmarks", "calc", +and "splath3". + +=back + Add a function and other objects to the plot. $plot->add_function('-16t^2 + 80t + 384', 't', 0, 8, color => 'blue', width => 3); @@ -59,11 +107,11 @@ =head2 DATASETS can be added individually, or multiple at once as shown: # Add a single dataset - $plot->add_dataset([$x1, $y1], [$x2, $y2], ..., [$xn, $yn], @options); + $plot->add_dataset([$x1, $y1], [$x2, $y2], ..., [$xn, $yn], %options); # Add multiple datasets with single call $plot->add_dataset( - [[$x11, $y11], [$x12, $y12], ..., [$x1n, $y1n], @options1], - [[$x21, $y21], [$x22, $y22], ..., [$x2m, $y2m], @options2], + [[$x11, $y11], [$x12, $y12], ..., [$x1n, $y1n], %options1], + [[$x21, $y21], [$x22, $y22], ..., [$x2m, $y2m], %options2], ... ); @@ -90,11 +138,11 @@ =head2 PLOT FUNCTIONS functions can be added individually or multiple at once: # Add a single function - $plot->add_function($function, $variable, $min, $max, @options) + $plot->add_function($function, $variable, $min, $max, %options) # Add multiple functions $plot->add_function( - [$function1, $variable1, $min1, $max1, @options1], - [$function2, $variable2, $min2, $max2, @options2], + [$function1, $variable1, $min1, $max1, %options1], + [$function2, $variable2, $min2, $max2, %options2], ... ); @@ -162,41 +210,81 @@ =head2 PLOT FUNCTIONS =head2 PLOT MULTIPATH FUNCTIONS -A multipath function is defined using multiple parametric paths pieced together into into a single -curve, whose primary use is to create a closed region to be filled using multiple boundaries. -This is done by providing a list of parametric functions, the name of the parameter, and a list -of options. +A multipath function is defined using multiple parametric paths and points +pieced together into into a single curve. This is done by providing a list of +parametric functions and points, the name of the parameter, and a list of +options. A parametric function is specified by a reference to an array +containing an x function, a y function, the minimum value for the parameter, and +the maximum value for the parameter, followed by options. A point is specified +by a reference to an array containing the coordinates of the point. One reason +for creating a multipath is to create a closed region to be filled using +multiple boundaries. $plot->add_multipath( [ - [ $function_x1, $function_y1, $min1, $max1 ], - [ $function_x2, $function_y2, $min2, $max2 ], + [ $function_x1, $function_y1, $min1, $max1, %path_options1 ], + [ $function_x2, $function_y2, $min2, $max2, %path_options2 ], + [ $point_x1, $point_x2 ] + [ $function_x3, $function_y3, $min3, $max3, %path_options3 ], ... ], $variable, %options ); -The paths have to be listed in the order they are followed, but the minimum/maximum values -of the parameter can match the parametrization. The following example creates a sector of -radius 5 between pi/4 and 3pi/4, by first drawing the line (0,0) to (5sqrt(2),5/sqrt(2)), -then the arc of the circle of radius 5 from pi/4 to 3pi/4, followed by the final line from -(-5sqrt(2), 5sqrt(2)) back to the origin. +Note that C<%path_options> can be specified for each path. At this point, the +only supported individual path option is C, if specified, then that +number of steps will be used for that path in the TikZ format. If not specified +the number of steps for the multipath will be used. That defaults to 30, but can +be changed by passing the C option in the general C<%options> for the +multipath. + +The paths have to be listed in the order they are followed, but the +minimum/maximum values of the parameter can match the parametrization. The +following example creates a sector of radius 5 between pi/4 and 3pi/4, by first +drawing the arc of the circle of radius 5 from pi/4 to 3pi/4, followed by the +line from (-5 sqrt(2), 5 sqrt(2)) to the origin, and then drawing the line from +the origin to (5 sqrt(2), 5 sqrt(2)). $plot->add_multipath( [ - [ 't', 't', 0, '5/sqrt(2)' ], - [ '5cos(t)', '5sin(t)', 'pi/4', '3pi/4' ], - [ '-t', 't', '5/sqrt(2)', 0 ], + [ '5cos(t)', '5sin(t)', 'pi/4', '3pi/4' ], + [ 0, 0 ], ], 't', color => 'green', fill => 'self', + cycle => 1 + ); + +Note that the ending point of one path does not need to be the same as the +starting point of the next. In this case a line segment will connect the end of +the first path to the start of the next. Additionally, if C<< cycle => 1 >> is +added to the C<%options> for the multipath, and the last path does not end where +the first path starts, then a line segment will connect the end of the last path +to the start of the first path. For example, the following path draws the top +half of a circle of radius two centered at the point (0, 2), followed by the +line segment from (-2, 0) to (2, 0). The line segment from (-2, 2) to (-2, 0) is +implicitly added to connect the end of the first path to the beginning of the +second path. The cycle option is added to close the path with the line segment +from (2, 0) to (2, 2). Note that drawing of the line is optimized by using only +2 steps, and the fill region is drawn on the "axis background" layer. + + $plot->add_multipath( + [ + [ '2 cos(t) + 5', '2 sin(t) - 5', '0', 'pi' ], + [ 't', '-8', '3', '7', steps => 2 ] + ], + 't', + color => 'green', + fill => 'self', + fill_layer => 'axis background', + cycle => 1 ); =head2 PLOT CIRCLES -Circles can be added to the plot by specifing its center and radius using the +Circles can be added to the plot by specifying its center and radius using the C<< $plot->add_circle >> method. This can either be done either one at a time or multiple at once. @@ -209,18 +297,37 @@ =head2 PLOT CIRCLES =head2 PLOT ARCS -Arcs (or a portion of a circle) can be plotted using the C<< $plot->add_arc >> method. -This method takes three points. The first point is where the arc starts, the second point -is the center of the circle, and the third point specifies the ray from the center of -the circle the arc ends. Arcs always go in the counter clockwise direction. +Arcs (or a portion of a circle) can be plotted using the C<< $plot->add_arc >> +method. This method takes three points. The first point is the center of the +circle, the second point is where the arc starts, and the arc ends at the point +on the circle that intersects the ray from the center of the circle pointing in +the direction of the third point. Arcs always go in the counter clockwise +direction. - $plot->add_arc([$start_x, $start_y], [$center_x, $center_y], [$end_x, $end_y], %options); + $plot->add_arc([$center_x, $center_y], [$start_x, $start_y], [$end_x, $end_y], %options); $plot->add_arc( - [[$start_x1, $start_y1], [$center_x1, $center_y1], [$end_x1, $end_y1], %options1], - [[$start_x2, $start_y2], [$center_x2, $center_y2], [$end_x2, $end_y2], %options2], + [[$center_x1, $center_y1], [$start_x1, $start_y1], [$end_x1, $end_y1], %options1], + [[$center_x2, $center_y2], [$start_x2, $start_y2], [$end_x2, $end_y2], %options2], + ... + ); + +=head2 PLOT RECTANGLES + +A rectangle can be plotted with the C<< $plot->add_rectangle >> method. This +method takes two points which are opposite corners of the rectangle. Multiple +rectangles can be plotted at once by passing references to arrays of data for +each rectangle. + + $plot->add_rectangle([$lower_left_x, $lower_left_y], [$upper_right_x, $upper_right_y], %options); + $plot->add_rectangle( + [[$lower_left_x1, $lower_left_y1], [$upper_right_x1, $upper_right_y1], %options1], + [[$lower_left_x2, $lower_left_y2], [$upper_right_x2, $upper_right_y2], %options2], ... ); +Note that if the C option is set, then C<< fill => 'self' >> is +automatically set, and so you do not need to specify that option. + =head2 PLOT VECTOR FIELDS Vector fields and slope fields can be plotted using the C<< $plot->add_vectorfield >> method. @@ -262,8 +369,9 @@ =head2 PLOT VECTOR FIELDS =item xsteps, ysteps -The number of arrows drawn in each direction. Note, that in TikZ output, this cannot be -set individually so only C is used. Default: 15 +The number of steps from the domain minimum to the domain maximum at which to +draw arrows. The number of arrows drawn will be one more than the number of +steps. Default: 15 =item scale @@ -303,14 +411,17 @@ =head2 DATASET OPTIONS =item width -The line width of the plot. Default: 1 +The line width of the plot. Default: 2 =item linestyle -Linestyle can be one of 'solid', 'dashed', 'dotted', 'short dashes', 'long dashes', -'long medium dashes' (alternates between long and medium dashes), or 'none'. If set -to 'none', only the points are shown (see marks for point options) For convince -underscores can also be used, such as 'long_dashes'. Default: 'solid' +Linestyle can be one of 'solid', 'dashed', 'dotted', 'short dashes', 'long +dashes', 'long medium dashes' (alternates between long and medium dashes), or +'none'. If set to 'none', then the curve will not be drawn. This can be used to +show only points by setting the C option (see C for point +options), or to only show a fill region by setting the C option. For +convenience underscores can also be used, such as 'long_dashes'. +Default: 'solid' =item marks @@ -322,8 +433,7 @@ =head2 DATASET OPTIONS =item mark_size Configure the size of the marks (if shown). The size is a natural number, -and represents the point (pt) size of the mark. If the size is 0, the -default size is used. Default: 0 +and represents the point (pt) size of the mark. Default: 2 =item start_mark @@ -338,7 +448,7 @@ =head2 DATASET OPTIONS =item arrow_size Sets the arrow head size for C or C arrows. -Default: 10 +Default: 8 =item name @@ -352,8 +462,9 @@ =head2 DATASET OPTIONS If set to 'self', the object fills within itself, best used with closed datasets. If set to 'xaxis', this will fill the area between the curve and the x-axis. If set to another non-empty string, this is the name of -the other dataset to fill against. The C attribute must be set to -fill between the 'xaxis' or another curve. +the other dataset to fill against. Note that the other dataset must be +created first before attempting to fill against it. The C attribute +must be set to fill between the 'xaxis' or another curve. The following creates a filled rectangle: @@ -392,6 +503,23 @@ =head2 DATASET OPTIONS fill_opacity => 0.2, ); +The following fills the area between the two curves x = 4 - y^2 and x = y^2 - 4, +and only fills in the area between y=-2 and y=2: + + $plot->add_function(['4 - y^2', 'y'], 'y', -3, 3, + color => 'blue', + name => 'A' + ); + $plot->add_function(['y^2 - 4', 'y'] 'y', -3, 3, + color => 'blue', + name => 'B', + fill => 'A', + fill_min_y => -2, + fill_max_y => 2, + fill_color => 'green', + fill_opacity => 0.2, + ); + =item fill_color The color used when filling the region. Default: C @@ -406,6 +534,49 @@ =head2 DATASET OPTIONS not defined, then the fill will use the full domain of the function. Default: undefined +=item fill_min_y, fill_max_y + +The minimum and maximum y-values to fill between. If either of these are +not defined, then the fill will use the full y range of the function. +Default: undefined + +Note that fill_min, fill_max, fill_min_y, and fill_max_y can be defined, and +this will result in the region filled being restricted to the rectangle defined +by those ranges. + +=item fill_reverse + +This should only be used if the TikZ output is filling the wrong region when +filling between two curves. By default the pgfplots fillbetween library +attempts to autodetect if one of the curves needs to be reversed in order to +obtain the fill region between the curves. However, the heuristic used for the +autodetection is sometimes wrong. Particularly when filling between two +functions x of y using the C and C options. In that case +set this option to either 0 or 1. If set to 1, then a reversal of one of the +curves is forced. If set to 0, then no reversal will occur. If this is unset, +then the pgfplots fillbetween library will attempt to autodetect if a reversal +is needed. If the correct region is being filled as compared to the JSXGraph +output, then leave this unset. Default: undefined + +=item layer + +The layer to draw on. Available layers are "axis background", "axis grid", +"axis ticks", "axis lines", "axis tick labels", "pre main", "main", +"axis descriptions", and "axis foreground". Note that the default order is the +order just given (from back to front). However, if C is true for +the axes, then "pre main" and "main" are after "axis background" and before +"axis grid". If this is undefined, then the default drawing layer will be used. +Default: undefined + +=item fill_layer + +The layer to place the fill region on. The curves will be drawn on the default +layer (or the layer specified by the C option) and the fill region will +be drawn on the layer specified by this option. Note that if this option is not +specified and the C option, then the curve and the fill region will both +be drawn on the specified C. See the C option above regarding +available layers to choose from. Default: undefined + =item steps This defines the number of points to generate for a dataset from a function. @@ -438,7 +609,7 @@ =head2 DATASET OPTIONS =item tikz_options -Additional pgfplots C<\addplot> options to be appeneded to the tikz output. +Additional pgfplots C<\addplot> options to be appended to the tikz output. =back @@ -448,13 +619,16 @@ =head2 LABELS Similar to datasets this can be added individually or multiple at once. # Add a label at the point ($x, $y). - $plot->add_label($x, $y, label => $label, @options)> + $plot->add_label($x, $y, $label, %options) # Add multiple labels at once. $plot->add_label( - [$x1, $y1, label => $label1, @options1], - [$x2, $y2, label => $label2, @options2], + [$x1, $y1, $label1, %options1], + [$x2, $y2, $label2, %options2], ... - ); + ); + + # Deprecated way of adding a label with an option instead of the third argument. + $plot->add_label($x, $y, label => $label, %options) Labels can be configured using the following options: @@ -470,8 +644,11 @@ =head2 LABELS =item fontsize -The font size of the label used. This can be one of 'tiny', 'small', 'medium', -'large', or 'giant'. Default: 'medium' +The font size of the label used. This can be one of 'tiny', 'small', +'normalsize', 'large', 'Large', 'huge', or 'Huge' which correspond to the same +named TeX font sizes. Note that this list used to include 'medium' and 'giant' +which still work, but are deprecated. Instead of 'medium' use 'normalsize', and +instead of 'giant' use 'Large'. Default: 'normalsize' =item rotate @@ -489,6 +666,21 @@ =head2 LABELS that states which end of the label is placed at the label's position. Can be one of 'top', 'middle', or 'bottom'. Default: 'middle' +=item anchor + +The angle in degrees of the label anchor relative to the center of the text. In +other words, the text will be positioned relative to the point on the rectangle +encompassing the label text (including C) where a ray shot from the +text center with the given angle hits the rectangle. This is an alternate method +for positioning the text relative to the label position. If this is set, then +C and C are not used. This is particularly useful for +positioning text when labeling angles. Default: '' + +=item padding + +This is the horizontal and vertical padding applied to the text of the label (in +pixels for the JSXGraph format, and in points for the TikZ format). Default: 4 + =item jsx_options An hash reference of options to pass to JSXGraph text object. @@ -499,38 +691,50 @@ =head2 LABELS =back +=head2 POINTS + +Points are really dataset marks (with no associated curve). Note that points +are drawn after all of the other graph objects except labels are drawn. Thus +points will always appear to be on top of everything else (except labels). + +Note that the C, C, C, and C dataset options are +valid for points. The C option is also a valid option that is an alias for +the C dataset option. The C or C options can be used to +change the symbol that is used for the point. By default the symbol is a +C. + + # Add a single point. + $plot->add_point($x1, $y1, color => $color, mark_size => $mark_size); + + # Add multiple points. + $plot->add_point( + [$x1, $y1, color => $color1, mark_size => $mark_size1], + [$x2, $y2, color => $color2, mark_size => $mark_size2], + ... + ); + + # Add a single open point. + $plot->add_point($x1, $y1, color => $color, mark => 'open_circle'); + =head2 STAMPS +Stamps and the C method are deprecated. DO NOT USE THEM. Use the +C or C methods instead. + Stamps are a single point with a mark drawn at the given point. Stamps can be added individually or multiple at once: # Add a single stamp. $plot->add_stamp($x1, $y1, symbol => $symbol, color => $color, radius => $radius); - # Add Multple stamps. + # Add Multiple stamps. $plot->add_stamp( [$x1, $y1, symbol => $symbol1, color => $color1, radius => $radius1], [$x2, $y2, symbol => $symbol2, color => $color2, radius => $radius2], ... ); -Stamps are here for backwards compatibility with WWplot and GD output, and are -equivalent to creating a dataset with one point when not using GD output (with -the small difference that stamps are added after all other datasets have been added). - -=head2 FILL REGIONS - -Fill regions define a point which GD will fill with a color until it hits a boundary curve. -This is only here for backwards comparability with WWplot and GD output. This will not -work with TikZ output, instead using the fill methods mentioned above. - - # Add a single fill region. - $plot->add_fill_region($x1, $y1, $color); - # Add multiple fill regions. - $plot->add_fill_region( - [$x1, $y1, $color1], - [$x2, $y2, $color2], - ... - ); +Adding a stamp is equivalent to creating a dataset with one point with the +exception that stamps are added after all other datasets have been added. =head2 COLORS @@ -564,6 +768,45 @@ =head2 COLORS ... ); +Note that SVG colors can also be used directly by name without being defined via +the C method. See section 4.3 of the +L +documentation for a list of available color names. + +=head2 EXTRA CODE + +Additional JavaScript and TikZ code may be added to draw elements that are not +provided for by this macro and its underlying modules. To add JavaScript code +set the C key on the C<$plot> object, and to add TikZ code set +the C key on the C<$plot> object. The JavaScript code will have +access to the C object via the variable C, and will be +inserted after all of the other code generated by this macro, and before the +C call is executed. The TikZ code will be inserted +after all of the other code generated by this macro, and before the pgfplots +C environment is ended. + +Note that if one of these is used, then both should be used to ensure that both +the JavaScript plot image (used in HTML) and the TikZ plot image (used in +hardcopy) are the same (or at least as close as possible). + +For example, + + $plot = Plot(); + + $plot->{extra_js_code} = << 'END_JS_CODE'; + board.create( + 'line', + [[0, 0], [1, 1]], + { straightLast: false, straightFirst: false, color: 'blue' } + ); + END_JS_CODE + + $plot->{extra_tikz_code} = "\draw[line width = 2pt, blue] (axis cs: 0, 0) -- (axis cs: 1, 1);"; + +Note that the above code is not an actual example that should be used as those +lines could be created using this macro directly. It is only included here to +demonstrate how to use these options. + =head1 TIKZ DEBUGGING When using Tikz output, the pgfplots code used to create the plot is stored in C<< $plot->{tikzCode} >>, diff --git a/macros/math/LinearProgramming.pl b/macros/math/LinearProgramming.pl index 9ae346ac9b..04cfc4d2d7 100644 --- a/macros/math/LinearProgramming.pl +++ b/macros/math/LinearProgramming.pl @@ -57,7 +57,7 @@ =head2 lp_pivot # perform a pivot operation # lp_pivot([[1,2,3],...,[4,5,6]], row, col, fractionmode) -# row and col indecies start at 0 +# row and col indices start at 0 # ^function lp_pivot sub lp_pivot { my $a_ref = shift; diff --git a/macros/math/MatrixReduce.pl b/macros/math/MatrixReduce.pl index a545655b45..920e4ee090 100644 --- a/macros/math/MatrixReduce.pl +++ b/macros/math/MatrixReduce.pl @@ -201,7 +201,7 @@ sub rref_perl_array { } # This was written by Davide Cervone. -# http://webwork.maa.org/moodle/mod/forum/discuss.php?d=2970 +# https://forums.openwebwork.org/mod/forum/discuss.php?d=2970 sub change_matrix_entry { my $self = shift; diff --git a/macros/math/PGdiffeqmacros.pl b/macros/math/PGdiffeqmacros.pl index 6e8c6385ae..28bc33ee9f 100644 --- a/macros/math/PGdiffeqmacros.pl +++ b/macros/math/PGdiffeqmacros.pl @@ -140,7 +140,7 @@ =head2 rad($num1,$num2,$num3) =cut sub rad { - # initalize primes + # initialize primes my @p = (2, 3, 5, 7, 11, 13, 17, 19, 23, 29); my ($a, $b, $c) = @_; my $s = "0"; @@ -345,7 +345,7 @@ sub ivy { $answer = "($c1) *$rr1 + ($c2)*t *$rr2"; } - # if the descriminant is negative, then the roots are imaginary. + # if the discriminant is negative, then the roots are imaginary. # recall, e^x where x=a+ib then e^x = (e^a)*cos(bt) + (e^a)*sin(bt). if ($d < 0) { # y(t) = me^(-Bt/(2A))*cos(t*sqrt(4AC-B*B)/(2A))+(2An+Bm)*sqrt(4AC-B*B)/(4AC-B*B)*e^(-Bt/(2A))*sin(t*sqrt(4AC-B*B)/(2A)) diff --git a/macros/math/PGmatrixmacros.pl b/macros/math/PGmatrixmacros.pl index df44d23f9f..ba663dfb30 100644 --- a/macros/math/PGmatrixmacros.pl +++ b/macros/math/PGmatrixmacros.pl @@ -116,7 +116,7 @@ sub display_matrix { if (ref($ra_matrix) eq 'Value::Matrix') { $ra_matrix = $ra_matrix->wwMatrix->array_ref; # translate } - if (ref($ra_matrix) eq 'Matrix') { #handle Real::Matrix1 type matrices: #FIXME deprectated + if (ref($ra_matrix) eq 'Matrix') { #handle Real::Matrix1 type matrices: #FIXME deprecated ($numRows, $numCols) = $ra_matrix->dim(); for (my $i = 0; $i < $numRows; $i++) { $myRows[$i] = []; @@ -285,14 +285,14 @@ sub dm_mat_left { or $main::displayMode eq 'HTML') { $out .= "$brh$erh"; - $out .= dm_image_delimeter($numrows, $opts{'left'}); + $out .= dm_image_delimiter($numrows, $opts{'left'}); # $out .= "$brh\n$erh"; return $out; } # Mode is now tth $out .= "'; return $out; } @@ -351,7 +351,7 @@ sub dm_end_matrix { } # Make an image of a big delimiter for a matrix -sub dm_image_delimeter { +sub dm_image_delimiter { my $numRows = shift; my $char = shift; my ($out, $j); @@ -374,8 +374,8 @@ sub dm_image_delimeter { } # Basically uses a table of special characters and simple -# recipe to produce big delimeters a la tth mode -sub dm_tth_delimeter { +# recipe to produce big delimiters a la tth mode +sub dm_tth_delimiter { my $numRows = shift; my $char = shift; @@ -390,7 +390,7 @@ sub dm_tth_delimeter { elsif ($char eq "]") { ($top, $mid, $bot, $extra) = ('ù', 'ú', 'û', 'ú'); } elsif ($char eq "{") { ($top, $mid, $bot, $extra) = ('ì', 'ï', 'î', 'í'); } elsif ($char eq "}") { ($top, $mid, $bot, $extra) = ('ü', 'ï', 'þ', 'ý'); } - else { warn "Unknown delimiter in dm_tth_delimeter"; } + else { warn "Unknown delimiter in dm_tth_delimiter"; } # old version # $out = ''; } elsif ($opts{'isfirst'}) { # solid line in tth mode $out .= ''; - $out .= dm_tth_delimeter($opts{'isfirst'} - 1, "|"); + $out .= dm_tth_delimiter($opts{'isfirst'} - 1, "|"); $out .= '
    "; - $out .= dm_tth_delimeter($numrows, $opts{'left'}); + $out .= dm_tth_delimiter($numrows, $opts{'left'}); # $out .= "\n"; return $out; } @@ -315,13 +315,13 @@ sub dm_mat_right { { $out .= "$brh
    $erh"; - $out .= dm_image_delimeter($numrows, $opts{'right'}); + $out .= dm_image_delimiter($numrows, $opts{'right'}); return $out; } # $out .= "
    "; $out .= '
    '; - $out .= dm_tth_delimeter($numrows, $opts{'right'}); + $out .= dm_tth_delimiter($numrows, $opts{'right'}); $out .= ''; @@ -463,13 +463,13 @@ sub dm_mat_row { if ($myalign eq "|" or $myalign eq "d") { if ($opts{'isfirst'} && $main::displayMode ne 'HTML_tth') { $out .= $brh . '' . $erh; - $out .= dm_image_delimeter($opts{'isfirst'} - 1, $myalign); + $out .= dm_image_delimiter($opts{'isfirst'} - 1, $myalign); } elsif ($main::displayMode eq 'HTML_tth') { if ($myalign eq "d") { # dashed line in tth mode $out .= ' |
    '; } } diff --git a/macros/math/PGmorematrixmacros.pl b/macros/math/PGmorematrixmacros.pl index 3965b2d400..753d30f347 100644 --- a/macros/math/PGmorematrixmacros.pl +++ b/macros/math/PGmorematrixmacros.pl @@ -172,7 +172,7 @@ sub linear_combo { # These should be compared to similar subroutines made later in # MatrixCheckers.pl -=head1 COMARISON FUNCTIONS +=head1 COMPARISON FUNCTIONS =head2 basis_cmp @@ -193,7 +193,7 @@ =head2 basis_cmp to zero debug -- if set to 1, provides verbose listing of - hash entries throughout fliters. + hash entries throughout filters. help -- 'none' (default) (is quiet on all errors) 'dim' (Tells student if wrong number of vectors are entered) @@ -483,8 +483,8 @@ sub vec_list_string { } } else { - # This parser code was origianally taken from PGanswermacros::check_syntax - # but parts of it needed to be slighty modified for this context + # This parser code was originally taken from PGanswermacros::check_syntax + # but parts of it needed to be slightly modified for this context my $parser = new AlgParserWithImplicitExpand; my $ret = $parser->parse($entry); #for use with loops @@ -588,7 +588,7 @@ sub ans_array_filter { # assign_option_aliases( \%opt, # ); set_default_options(\%options, _filter_name => 'ans_array_filter',); -# $rh_ans->{ans_label} =~ /$ArRaY(\d+)\[\d+,\d+,\d+\]/; # CHANGE made to accomodate HTML 4.01 standards for name attribute +# $rh_ans->{ans_label} =~ /$ArRaY(\d+)\[\d+,\d+,\d+\]/; # CHANGE made to accommodate HTML 4.01 standards for name attribute $rh_ans->{ans_label} =~ /$ArRaY(\d+)\_\_\d+\-\d+\-\d+\_\_/; my $ans_num = $1; my @keys = grep /$ArRaY$ans_num/, keys(%{$main::inputs_ref}); @@ -619,8 +619,8 @@ sub ans_array_filter { for ($k = 0; $k < scalar(@{ $array[$i][$j] }); $k++) { my $entry = $array[$i][$j][$k]; $entry = math_constants($entry); - # This parser code was origianally taken from PGanswermacros::check_syntax - # but parts of it needed to be slighty modified for this context + # This parser code was originally taken from PGanswermacros::check_syntax + # but parts of it needed to be slightly modified for this context my $parser = new AlgParserWithImplicitExpand; my $ret = $parser->parse($entry); #for use with loops diff --git a/macros/math/PGnauGraphtheory.pl b/macros/math/PGnauGraphtheory.pl index f81adf6afa..64afde198a 100644 --- a/macros/math/PGnauGraphtheory.pl +++ b/macros/math/PGnauGraphtheory.pl @@ -1535,7 +1535,7 @@ sub GRwheel_size { #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). +# size labels on top and bottom (and no labels). ###################################### sub GRcompletebipartite_size_size { my ($m, $n) = @_; diff --git a/macros/math/PGnauScheduling.pl b/macros/math/PGnauScheduling.pl index 2a5ba74237..562fdd1ce7 100644 --- a/macros/math/PGnauScheduling.pl +++ b/macros/math/PGnauScheduling.pl @@ -232,7 +232,7 @@ sub ListProc { @machine_info, @these_dones ); - #intialize and setup + #initialize and setup ##################################################### @coordinates_weights = split ",", $coordinates; $i = 0; @@ -274,7 +274,7 @@ sub ListProc { $dependence_count[ $connections[$j] ]++; $j += 2; } - # done intializing + # done initializing ########################################## $time = 0; $done = 0; @@ -290,7 +290,7 @@ sub ListProc { if ($ready[$j] == $i) { $onlist = 1; } $j++; } - # check readyness against current dependencies + # check readiness against current dependencies if (!$onlist && $dependence_count[$i] == 0) { unshift @ready, $i; pop @ready; @@ -559,7 +559,7 @@ sub CritList { $temp_word ); - #intialize and setup + #initialize and setup ##################################################### @coordinates_weights = split ",", $coordinates; @these_coordinates_weights = @coordinates_weights; @@ -577,7 +577,7 @@ sub CritList { $task_count = scalar @weights; @connection_array = split ",", $connections; $priority_list = ""; - # end intialize and setup + # end initialize and setup ##################################################### $scheduled_count = 0; while ($scheduled_count < $task_count) { @@ -645,7 +645,7 @@ sub CritPath { $independent ); - #intialize and setup + #initialize and setup ##################################################### @coordinates_weights = split ",", $coordinates; $i = 0; @@ -661,7 +661,7 @@ sub CritPath { @connection_array = split ",", $connections; $i = 0; while ($i < scalar @connection_array) { $connection_array[$i]--; $i++; } - # end intialize and setup + # end initialize and setup ##################################################### # find all possible starting vertices ... diff --git a/macros/math/PGnauStats.pl b/macros/math/PGnauStats.pl index 6762dd59c2..47fa30c96a 100644 --- a/macros/math/PGnauStats.pl +++ b/macros/math/PGnauStats.pl @@ -1055,7 +1055,7 @@ sub DrawNormalDist { ); ########################### - # begin intialize and setup + # begin initialize and setup # initialize to default # check for options while (@opt) { diff --git a/macros/math/PGstatisticsmacros.pl b/macros/math/PGstatisticsmacros.pl index 44f690d690..6e48d3c2b7 100644 --- a/macros/math/PGstatisticsmacros.pl +++ b/macros/math/PGstatisticsmacros.pl @@ -124,7 +124,7 @@ =head2 stats_mean stats_mean(@data); -Computes the artihmetic mean of a list of numbers, data. You may also pass the numbers individually. +Computes the arithmetic mean of a list of numbers, data. You may also pass the numbers individually. =cut @@ -284,7 +284,7 @@ =head2 exprand sub exprand { # generate exponentially dist. numbers Exp(x,lambda) # exprand(lambda,N,digits) - # Generates N random numbers. The distribution is exponetially + # Generates N random numbers. The distribution is exponentially # distributed with parameter lambda. The value of 'digits' gives the # number of decimal places to return. my ($lambda, $N, $digits) = @_; @@ -501,7 +501,7 @@ =head2 discreterand This routine will generate num random results. The distribution is in the given array. Each element in the array is itself an array. The first value in the array is the probability. The second value in the -array is the value assocated with the probability. +array is the value associated with the probability. =cut @@ -511,7 +511,7 @@ sub discreterand { # generate random, values based on a given table # Generates num random results. The distribution is in the given array. # Each element in the array is itself an array. # The first value in the array is the probability. - # The second value in the array is the value assocated with the probability. + # The second value in the array is the value associated with the probability. my $num = shift; # Number of values to generate my @table = @_; # Table of arrays with the probabilities and values. @@ -1141,14 +1141,14 @@ =head2 frequencies %freq = frequencies(@theData) -Finds the factors in the data set and calculates the frequency of occurance for each factor. +Finds the factors in the data set and calculates the frequency of occurrence for each factor. Returns a hash whose keys are the factors and the associated values are the frequencies. =cut sub frequencies { # %freq = frequencies(@theData) - # returns a hash whos keys are the factors and the associated values are the frequencies. + # returns a hash whose keys are the factors and the associated values are the frequencies. # Get the data that is passed to me and put it all in one array. my (@data_list) = @_; diff --git a/macros/math/SimpleGraph.pl b/macros/math/SimpleGraph.pl new file mode 100644 index 0000000000..451b9f733d --- /dev/null +++ b/macros/math/SimpleGraph.pl @@ -0,0 +1,2799 @@ +BEGIN { strict->import; } + +loadMacros('PGauxiliaryFunctions.pl', 'PGbasicmacros.pl', 'MathObjects.pl', 'plots.pl'); + +sub _SimpleGraph_init { + my $context = $main::context{EdgeSet} = Parser::Context->getCopy('Numeric'); + $context->{name} = 'EdgeSet'; + $context->{value}{EdgeSet} = 'GraphTheory::SimpleGraph::Value::EdgeSet'; + $context->{value}{Edge} = 'GraphTheory::SimpleGraph::Value::Edge'; + $context->lists->set( + # The "List" type list is set to use the GraphTheory::SimpleGraph::Parser::List class so that implicit lists can + # be turned into edges or edge sets, and empty lists can be interpreted as edge sets for a graph with no edges. + # All explicit lists that open with something other than a brace are untouched. + List => { %{ $context->lists->get('List') }, class => 'GraphTheory::SimpleGraph::Parser::List' }, + EdgeSet => + { class => 'GraphTheory::SimpleGraph::Parser::List', open => '{', close => '}', separator => ', ' }, + Edge => { class => 'GraphTheory::SimpleGraph::Parser::List', open => '{', close => '}', separator => ', ' }, + ); + $context->parens->set( + '{' => { close => '}', type => 'Edge', formList => 1, formMatrix => 0, removable => 0, emptyOK => 1 }); +} + +sub SimpleGraph { GraphTheory::SimpleGraph->new(@_) } +sub EdgeSet { GraphTheory::SimpleGraph::Value::EdgeSet->new(@_) } +sub Edge { GraphTheory::SimpleGraph::Value::Edge->new(@_) } + +sub randomSimpleGraph { + my ($size, %options) = @_; + + my $graph; + my $edgeCount = 0; + my $edgeProbability = $options{edgeProbability} // 0.5; + + if (ref $size eq 'ARRAY') { + $graph = + GraphTheory::SimpleGraph->new($size->[0] * $size->[1], %options, gridLayout => [ $size->[0], $size->[1] ]); + + for my $i (0 .. $size->[0] - 1) { + for my $j (0 .. $size->[1] - 1) { + my $location = $j * $size->[0] + $i; + + if ($j < $size->[1] - 1 && main::random(0, 100) <= 100 * $edgeProbability) { + $graph->addEdge($location, $location + $size->[0]); + ++$edgeCount; + } + if ($i < $size->[0] - 1 && main::random(0, 100) <= 100 * $edgeProbability) { + $graph->addEdge($location, $location + 1); + ++$edgeCount; + } + if ($i < $size->[0] - 1 && $j < $size->[1] - 1 && main::random(0, 100) <= 100 * $edgeProbability) { + $graph->addEdge($location, $location + $size->[0] + 1); + ++$edgeCount; + } + if ($i >= 1 && $j < $size->[1] - 1 && main::random(0, 100) <= 100 * $edgeProbability) { + $graph->addEdge($location, $location + $size->[0] - 1); + ++$edgeCount; + } + } + } + } else { + $graph = GraphTheory::SimpleGraph->new($size, %options); + + for my $i (0 .. $size - 1) { + for my $j (0 .. $i - 1) { + if (main::random(0, 100) <= 100 * $edgeProbability) { + $graph->addEdge($i, $j); + ++$edgeCount; + } + } + } + } + + return $graph->setRandomWeights(%options, edgeCount => $edgeCount); +} + +sub randomGraphWithEulerCircuit { + my ($size, %options) = @_; + + die 'A graph with an Euler circuit must have at least 5 vertices.' if $size < 5; + + # Remove these from the options so that setting weights is deferred until the return. + my ($startEdgeWeight, $edgeWeightIncrement, $edgeWeightRange) = + delete @options{qw(startEdgeWeight edgeWeightIncrement edgeWeightRange)}; + + my $graph; + + do { + $graph = simpleGraphWithDegreeSequence([ map { main::random(2, $size - 1, 2) } 0 .. $size - 1 ], %options); + } while !defined $graph || $graph->numComponents > 1; + + return $graph->setRandomWeights( + %options, + startEdgeWeight => $startEdgeWeight, + edgeWeightIncrement => $edgeWeightIncrement, + edgeWeightRange => $edgeWeightRange + )->shuffle; +} + +sub randomGraphWithEulerTrail { + my ($size, %options) = @_; + + die 'A graph with an Euler trail must have at least 5 vertices.' if $size < 5; + + # Remove these from the options so that setting weights is deferred until the return. + my ($startEdgeWeight, $edgeWeightIncrement, $edgeWeightRange) = + delete @options{qw(startEdgeWeight edgeWeightIncrement edgeWeightRange)}; + + my $graph = randomGraphWithEulerCircuit($size, %options); + + my ($vertex1, $vertex2); + + do { + ($vertex1, $vertex2) = main::random_subset(2, 0 .. $size - 1); + } until $graph->hasEdge($vertex1, $vertex2); + + $graph->removeEdge($vertex1, $vertex2); + + return $graph->setRandomWeights( + %options, + startEdgeWeight => $startEdgeWeight, + edgeWeightIncrement => $edgeWeightIncrement, + edgeWeightRange => $edgeWeightRange + ); +} + +sub randomGraphWithoutEulerTrail { + my ($size, %options) = @_; + + die 'A graph without an Euler trail must have at least 5 vertices.' if $size < 5; + + # Remove these from the options so that setting weights is deferred until the return. + my ($startEdgeWeight, $edgeWeightIncrement, $edgeWeightRange) = + delete @options{qw(startEdgeWeight edgeWeightIncrement edgeWeightRange)}; + + my $graph; + + do { + $graph = simpleGraphWithDegreeSequence([ map { main::random(2, $size - 1) } 0 .. $size - 1 ], %options); + } while !defined $graph || $graph->hasEulerTrail; + + return $graph->setRandomWeights( + %options, + startEdgeWeight => $startEdgeWeight, + edgeWeightIncrement => $edgeWeightIncrement, + edgeWeightRange => $edgeWeightRange + )->shuffle; +} + +sub randomBipartiteGraph { + my ($size, %options) = @_; + + my ($s1, $s2); + + if (ref $size eq 'ARRAY' && @$size == 2) { + ($s1, $s2) = @$size; + die 'A bipartite graph must have at least 1 vertex in each partition.' unless $s1 > 0 && $s2 > 0; + } else { + die 'A bipartite graph must have at least 2 vertices.' if $size < 2; + $s1 = main::random(1, $size - 1); + $s2 = $size - $s1; + } + + my $graph = GraphTheory::SimpleGraph->new($s1 + $s2, %options); + + my $edgeProbability = $options{edgeProbability} // 0.5; + my $edgeCount = 0; + + for my $i (0 .. $s1 - 1) { + for my $j ($s1 .. $s1 + $s2 - 1) { + next unless main::random(0, 100) <= 100 * $edgeProbability; + $graph->addEdge($i, $j); + ++$edgeCount; + } + } + + return $graph->setRandomWeights(%options, edgeCount => $edgeCount)->shuffle; +} + +sub randomTreeGraph { + my ($size, %options) = @_; + + die 'A tree graph must have at least 2 vertices.' if $size < 2; + + my $graph = GraphTheory::SimpleGraph->new($size, %options); + + my @available = main::random_subset($size, 0 .. $size - 1); + + my @used; + push @used, pop @available; + do { + my $j = pop @available; + my $i = main::random(0, $#used); + $graph->addEdge($used[$i], $j); + push @used, $j; + } while @available > 0; + + return $graph->setRandomWeights(%options, edgeCount => @used - 1); +} + +sub randomForestGraph { + my ($size, %options) = @_; + + die 'A forest graph must have at least 2 vertices.' if $size < 2; + + # Remove these from the options so that setting weights is deferred until the return. + my ($startEdgeWeight, $edgeWeightIncrement, $edgeWeightRange) = + delete @options{qw(startEdgeWeight edgeWeightIncrement edgeWeightRange)}; + + my $graph = randomTreeGraph($size, %options); + + my ($vertex1, $vertex2); + do { + ($vertex1, $vertex2) = main::random_subset(2, 0 .. $size - 1); + } until $graph->hasEdge($vertex1, $vertex2); + $graph->removeEdge($vertex1, $vertex2); + + return $graph->setRandomWeights( + %options, + startEdgeWeight => $startEdgeWeight, + edgeWeightIncrement => $edgeWeightIncrement, + edgeWeightRange => $edgeWeightRange + ); +} + +# Returns a Hamiltonian graph of the given $size. Note that $size must be 5 or greater. +sub randomHamiltonianGraph { + my ($size, %options) = @_; + + die 'A Hamiltonian graph must have at least 5 vertices.' if $size < 5; + + my $graph = GraphTheory::SimpleGraph->new($size, %options); + + my $comp = $size * ($size - 1) / 2; + + my ($low, $high) = $size <= 5 ? ($size + 1, $comp - 1) : (int($comp / 3) + 1, int($comp / 2) + 1); + + for my $i (0 .. $size - 1) { + $graph->addEdge($i, ($i + 1) % $size); + } + + my $edges = int main::random($low, $high); + + my $edgeCount = $size; + while ($edgeCount < $edges) { + my ($i, $j) = main::random_subset(2, 0 .. $size - 1); + unless ($graph->hasEdge($i, $j)) { + $graph->addEdge($i, $j); + ++$edgeCount; + } + } + + return $graph->setRandomWeights(%options, edgeCount => $edgeCount)->shuffle; +} + +sub randomNonHamiltonianGraph { + my ($size, $type, %options) = @_; + + my $graph = GraphTheory::SimpleGraph->new($size); + my $edgeCount = 0; + + if ($type % 2 == 0) { + die 'A non Hamiltonian graph with a degree 1 vertex must have at least 5 vertices.' if $size < 5; + + my $numEdges = main::random($size + 1, ($size - 1) * ($size - 2) / 3 + 1); + + for my $i (0 .. $size - 2) { + $graph->addEdge($i, $i + 1); + } + $graph->addEdge($size - 2, 0); + + $edgeCount = $size; + do { + my ($i, $j) = main::random_subset(2, 0 .. $size - 2); + unless ($graph->hasEdge($i, $j)) { + $graph->addEdge($i, $j); + ++$edgeCount; + } + } while $edgeCount < $numEdges; + } else { + die 'A non Hamiltonian graph with two cycles must have at least 6 vertices.' if $size < 6; + + my $split = int($size / 2); + + for my $i (0 .. $split - 1) { + $graph->addEdge($i, ($i + 1) % $split); + } + for my $i ($split .. $size - 1) { + $graph->addEdge($i, ($i + 1) % $size); + } + $graph->addEdge($size - 1, $split); + + my $numEdges = 2 * $size - 5; + $edgeCount = $size + 1; + + my $maxtry = 4; # Protection against a possibly infinite loop. + while ($edgeCount < $numEdges && $maxtry > 0) { + --$maxtry; + my $v1 = main::random(0, $split - 1); + my $v2 = ($v1 + 2) % $split; + unless ($graph->hasEdge($v1, $v2)) { + $graph->addEdge($v1, $v2); + ++$edgeCount; + } + unless ($graph->hasEdge($v1 + $split, $v2 + $split)) { + $graph->addEdge($v1 + $split, $v2 + $split); + ++$edgeCount; + } + } + } + + return $graph->setRandomWeights(%options, edgeCount => $edgeCount)->shuffle; +} + +sub simpleGraphWithDegreeSequence { + my ($degrees, %options) = @_; + + my @degrees = reverse num_sort(@$degrees); + + return if $degrees[0] >= @degrees; + + my $graph = GraphTheory::SimpleGraph->new(scalar @degrees, %options); + + my $value = 0; + my $vertex = 0; + my $edgeCount = 0; + + while ($vertex < @degrees && $value == 0) { + $value = $degrees[$vertex] - $graph->vertexDegree($vertex); + my $otherVertex = $vertex + 1; + while ($value > 0 && $otherVertex < @degrees) { + if ($graph->vertexDegree($otherVertex) < $degrees[$otherVertex]) { + $graph->addEdge($vertex, $otherVertex); + ++$edgeCount; + --$value; + } + ++$otherVertex; + } + ++$vertex; + } + + return if $value != 0; + + return $graph->setRandomWeights(%options, edgeCount => $edgeCount); +} + +sub cycleGraph { + my ($size, %options) = @_; + + my $graph = GraphTheory::SimpleGraph->new($size, %options); + + for (0 .. $graph->lastVertexIndex) { + $graph->addEdge($_, ($_ + 1) % $graph->numVertices); + } + + return $graph->setRandomWeights(%options, edgeCount => $graph->numVertices); +} + +sub completeGraph { + my ($size, %options) = @_; + + my $graph = GraphTheory::SimpleGraph->new($size, %options); + + for my $i (0 .. $size - 1) { + for my $j ($i + 1 .. $size - 1) { + $graph->addEdge($i, $j); + } + } + + return $graph->setRandomWeights(%options, edgeCount => $graph->numVertices * ($graph->numVertices - 1) / 2); +} + +sub wheelGraph { + my ($size, %options) = @_; + + my $graph = GraphTheory::SimpleGraph->new($size, %options, wheelLayout => 0); + + for my $i (1 .. $size - 2) { + $graph->addEdge($i, $i + 1); + $graph->addEdge(0, $i); + } + $graph->addEdge($size - 1, 1); + $graph->addEdge(0, $size - 1); + + return $graph->setRandomWeights(%options, edgeCount => ($graph->numVertices - 1) * 2); +} + +sub completeBipartiteGraph { + my ($m, $n, %options) = @_; + + my $graph = + GraphTheory::SimpleGraph->new($m + $n, %options, bipartiteLayout => [ [ 0 .. $m - 1 ], [ $m .. $m + $n - 1 ] ]); + + for my $i (0 .. $m - 1) { + for my $j ($m .. $m + $n - 1) { + $graph->addEdge($i, $j); + } + } + + return $graph->setRandomWeights(%options, edgeCount => $m * $n); +} + +package GraphTheory::SimpleGraph; + +sub new { + my ($class, $definition, %options) = @_; + my $self = bless {}, ref($class) || $class; + + die 'A graph definition in the form of a numeric size, adjacency matrix, ' + . 'edge set, or another simple graph object is required.' + unless defined $definition; + + if (ref $definition eq 'GraphTheory::SimpleGraph') { + $self->{adjacencyMatrix} = [ map { [@$_] } @{ $definition->adjacencyMatrix } ]; + $self->{labels} = [ @{ $definition->{labels} } ]; + $options{gridLayout} //= $definition->{gridLayout}; + $options{bipartiteLayout} //= $definition->{bipartiteLayout}; + $options{wheelLayout} //= $definition->{wheelLayout}; + } elsif (Value::classMatch($definition, 'Matrix') + || Value::classMatch($definition, 'EdgeSet') + || ref $definition eq 'ARRAY') + { + $definition = [ map { [ $_->value ] } @{ $definition->data } ] if Value::classMatch($definition, 'Matrix'); + $definition = $definition->data if Value::classMatch($definition, 'EdgeSet'); + + my $haveLabels = ref $options{labels} eq 'ARRAY' && @{ $options{labels} }; + die 'Graphs with no vertices are not supported.' unless @$definition || $haveLabels; + + my @edgeSet; + for (@$definition) { + if (ref $_ ne 'GraphTheory::SimpleGraph::Value::Edge') { @edgeSet = (); last; } + push(@edgeSet, $_->data); + } + if (!@edgeSet) { + for (@$definition) { + if ( + ref $_ ne 'ARRAY' + || @$_ < 2 + || @$_ > 3 + || (!Value::classMatch($_->[0], 'String') + && (Value::isReal($_->[0]) || ($_->[0] ^ $_->[0]) eq '0')) + || (!Value::classMatch($_->[1], 'String') + && (Value::isReal($_->[1]) || ($_->[1] ^ $_->[1]) eq '0')) + ) + { + @edgeSet = (); + last; + } + push(@edgeSet, $_); + } + } + + if (@edgeSet || (!@$definition && $haveLabels)) { + die 'Labels must be provided when using an edgeset definition.' unless $haveLabels; + + $definition = [ map { $_->{data} } @$definition ] + if ref $definition->[0] eq 'GraphTheory::SimpleGraph::Value::Edge'; + + $self->{labels} = [ @{ $options{labels} } ]; + $self->{adjacencyMatrix} = [ map { [ (0) x @{ $self->{labels} } ] } @{ $self->{labels} } ]; + + my %labelIndices = map { $self->{labels}[$_] => $_ } 0 .. $#{ $self->{labels} }; + + for my $i (0 .. $#$definition) { + die 'Invalid edge set format.' unless ref $definition->[$i] eq 'ARRAY'; + my @edge = @{ $definition->[$i] }; + die 'Invalid edge format.' unless @edge >= 2; + die "Invalid vertex $edge[0] in edge set." unless defined $labelIndices{ $edge[0] }; + die "Invalid vertex $edge[1] in edge set." unless defined $labelIndices{ $edge[1] }; + $self->edgeWeight($labelIndices{ $edge[0] }, $labelIndices{ $edge[1] }, $edge[2] // 1); + } + } else { + $self->{adjacencyMatrix} = []; + for my $i (0 .. $#$definition) { + die 'Invalid adjacency matrix format.' unless ref $definition->[$i] eq 'ARRAY'; + die 'The adjacency matrix for a graph must be a square matrix.' + unless @{ $definition->[$i] } == @$definition; + die 'The diagonal entries of the adjacency matrix must be zero.' if $definition->[$i][$i]; + for my $j ($i + 1 .. $#{ $definition->[$i] }) { + die 'The adjacency matrix for a graph must be symmetric.' + unless $definition->[$i][$j] == $definition->[$j][$i]; + } + push(@{ $self->{adjacencyMatrix} }, [ @{ $definition->[$i] } ]); + } + } + } else { + die 'Graphs with no vertices are not supported.' unless $definition > 0; + $self->{adjacencyMatrix} = [ map { [ (0) x $definition ] } 0 .. ($definition - 1) ]; + } + + if (ref $options{labels} eq 'ARRAY') { + die 'Not enough vertex labels provided.' if @{ $options{labels} } < $self->numVertices; + for (0 .. $self->lastVertexIndex) { + die 'Labels cannot be undefined.' unless defined $options{labels}[$_]; + } + $self->{labels} = [ @{ $options{labels} }[ 0 .. $self->lastVertexIndex ] ]; + } + + unless (defined $self->{labels}) { + my $alphaOffset = main::random(0, 25 - $self->lastVertexIndex); + $self->{labels} = [ ('A' .. 'Z')[ $alphaOffset .. $alphaOffset + $self->lastVertexIndex ] ]; + } + + $self->{gridLayout} = [ @{ $options{gridLayout} } ] + if ref $options{gridLayout} eq 'ARRAY' && @{ $options{gridLayout} } == 2; + + if (ref $options{bipartiteLayout} eq 'ARRAY' + && @{ $options{bipartiteLayout} } == 2 + && !grep { ref $_ ne 'ARRAY' } @{ $options{bipartiteLayout} }) + { + $self->{bipartiteLayout} = [ map { [@$_] } @{ $options{bipartiteLayout} } ]; + } elsif ($options{bipartiteLayout}) { + $self->{bipartiteLayout} = 1; + } + + $self->{wheelLayout} = $options{wheelLayout} if defined $options{wheelLayout}; + + return $self; +} + +sub adjacencyMatrix { + my $self = shift; + return $self->{adjacencyMatrix}; +} + +sub edgeSet { + my ($self, %options) = @_; + + my $context = Value::isContext($options{context}) ? $options{context} : Parser::Context->getCopy('EdgeSet'); + $self->addVerticesToContext($options{caseSensitive} // 1, $context); + + my @edgeSet; + for my $i (0 .. $self->lastVertexIndex) { + for my $j ($i + 1 .. $self->lastVertexIndex) { + next unless $self->hasEdge($i, $j); + push( + @edgeSet, + GraphTheory::SimpleGraph::Value::Edge->new( + $context, $self->vertexLabel($i), $self->vertexLabel($j) + ) + ); + $edgeSet[-1]->{open} = '{'; + $edgeSet[-1]->{close} = '}'; + } + } + + my $edgeSet = GraphTheory::SimpleGraph::Value::EdgeSet->new($context, \@edgeSet); + $edgeSet->{open} = '{'; + $edgeSet->{close} = '}'; + return $edgeSet; +} + +sub addVerticesToContext { + my ($self, $caseSensitive, $context) = @_; + $context = Value::isContext($context) ? $context : main::Context(); + $context->strings->add( + map { $_ => { caseSensitive => $caseSensitive // 1 } } + grep { !defined $context->strings->all->{$_} } @{ $self->labels } + ); + $context->strings->all->{$_}{isVertex} = 1 for @{ $self->labels }; + return; +} + +sub numVertices { + my $self = shift; + return scalar @{ $self->{adjacencyMatrix} }; +} + +sub lastVertexIndex { + my $self = shift; + return $#{ $self->{adjacencyMatrix} }; +} + +sub numEdges { + my $self = shift; + my $edgeCount = 0; + for my $i (0 .. $self->lastVertexIndex) { + for my $j (0 .. $i - 1) { + next unless $self->hasEdge($i, $j); + ++$edgeCount; + } + } + return $edgeCount; +} + +sub labels { + my ($self, $labels) = @_; + if (ref $labels eq 'ARRAY') { + die 'Not enough vertex labels provided.' if @$labels < $self->numVertices; + $self->{labels} = [ @$labels[ 0 .. $self->numVertices ] ]; + } + return $self->{labels}; +} + +sub labelsString { + my $self = shift; + return join($main::PG->maketext(', '), @{ $self->{labels} }); +} + +sub vertexLabel { + my ($self, $vertexIndex) = @_; + return $self->{labels}[$vertexIndex]; +} + +sub vertexIndex { + my ($self, $vertexLabel) = @_; + for (0 .. $#{ $self->{labels} }) { + return $_ if $vertexLabel eq $self->{labels}[$_]; + } + return -1; +} + +sub vertexDegree { + my ($self, $vertex) = @_; + my $degree = 0; + for my $j (0 .. $self->lastVertexIndex) { + ++$degree if $self->hasEdge($vertex, $j); + } + return $degree; +} + +sub degrees { + my $self = shift; + return map { $self->vertexDegree($_) } 0 .. $self->lastVertexIndex; +} + +sub numComponents { + my $self = shift; + + my @adjacencyMatrix = map { [@$_] } @{ $self->{adjacencyMatrix} }; + + my $result = @adjacencyMatrix; + for my $i (0 .. $#adjacencyMatrix) { + my $connected = 0; + for my $j ($i + 1 .. $#adjacencyMatrix) { + if ($adjacencyMatrix[$i][$j] != 0) { + ++$connected; + for my $k (0 .. $#adjacencyMatrix) { + $adjacencyMatrix[$j][$k] += $adjacencyMatrix[$i][$k]; + $adjacencyMatrix[$k][$j] += $adjacencyMatrix[$k][$i]; + } + } + } + --$result if $connected > 0; + } + return $result; +} + +sub components { + my $self = shift; + + my @adjacencyMatrix = map { [@$_] } @{ $self->{adjacencyMatrix} }; + + for my $i (0 .. $#adjacencyMatrix) { + for my $j ($i + 1 .. $#adjacencyMatrix) { + if ($adjacencyMatrix[$i][$j] != 0) { + for my $k (0 .. $#adjacencyMatrix) { + $adjacencyMatrix[$j][$k] += $adjacencyMatrix[$i][$k]; + $adjacencyMatrix[$k][$j] += $adjacencyMatrix[$k][$i]; + } + } + } + } + + my @components; + for my $i (reverse(0 .. $#adjacencyMatrix)) { + my $componentFound = 0; + for (@components) { + next unless $adjacencyMatrix[ $_->[-1] ][$i]; + $componentFound = 1; + unshift(@$_, $i); + last; + } + push(@components, [$i]) unless $componentFound; + } + + return main::PGsort(sub { $_[0][0] < $_[1][0] }, @components); +} + +sub edgeWeight { + my ($self, $i, $j, $weight) = @_; + if (defined $weight) { + $self->{adjacencyMatrix}[$i][$j] = $weight; + $self->{adjacencyMatrix}[$j][$i] = $weight; + } + return $self->{adjacencyMatrix}[$i][$j]; +} + +sub addEdge { + my ($self, $i, $j, $weight) = @_; + $self->edgeWeight($i, $j, $weight || 1); + return; +} + +sub removeEdge { + my ($self, $i, $j) = @_; + $self->edgeWeight($i, $j, 0); + return; +} + +sub hasEdge { + my ($self, $i, $j) = @_; + return $self->edgeWeight($i, $j) != 0; +} + +sub setRandomWeights { + my ($self, %options) = @_; + + my $incrementalRandom = + defined $options{startEdgeWeight} + && $options{startEdgeWeight} > 0 + && ($options{edgeWeightIncrement} // 1) > 0; + + return $self + unless $incrementalRandom + || (ref $options{edgeWeightRange} eq 'ARRAY' && @{ $options{edgeWeightRange} } >= 2); + + my $edgeCount = $options{edgeCount} // $self->numEdges; + + my @weights = + $incrementalRandom + ? main::random_subset($edgeCount, + map { $options{startEdgeWeight} + $_ * ($options{edgeWeightIncrement} // 1) } 0 .. $edgeCount - 1) + : map { main::random(@{ $options{edgeWeightRange} }) } 1 .. $edgeCount; + + for my $i (0 .. $self->lastVertexIndex) { + for my $j ($i + 1 .. $self->lastVertexIndex) { + $self->edgeWeight($i, $j, shift(@weights) // 1) if $self->hasEdge($i, $j); + } + } + + return $self; +} + +sub isEqual { + my ($self, $other) = @_; + return 0 unless ref $other eq 'GraphTheory::SimpleGraph'; + return 0 if @{ $self->{adjacencyMatrix} } != @{ $other->{adjacencyMatrix} }; + for my $i (0 .. $#{ $self->{adjacencyMatrix} }) { + return 0 if @{ $self->{adjacencyMatrix}[$i] } != @{ $other->{adjacencyMatrix}[$i] }; + for my $j (0 .. $i - 1) { + return 0 if $self->{adjacencyMatrix}[$i][$j] != $other->{adjacencyMatrix}[$i][$j]; + } + } + return 1; +} + +sub isIsomorphic { + my ($self, $other) = @_; + + return 0 unless ref $other eq 'GraphTheory::SimpleGraph' && $self->numVertices == $other->numVertices; + + my @degrees = main::num_sort($self->degrees); + my @otherDegrees = main::num_sort($other->degrees); + for (0 .. $#degrees) { + return 0 unless $degrees[$_] == $otherDegrees[$_]; + } + + my $permutations = [ [0] ]; + + for my $i (1 .. $self->lastVertexIndex) { + my @newPermutations; + for my $permutation (@$permutations) { + for my $j (0 .. @$permutation) { + my @new = @$permutation; + splice(@new, $j, 0, $i); + push(@newPermutations, \@new); + } + } + $permutations = \@newPermutations; + } + + # The last permutation is the original vertex order, so remove it. + pop @$permutations; + + for my $permutation (@$permutations) { + my @shuffledGraph; + for my $i (0 .. $other->lastVertexIndex) { + for my $j (0 .. $other->lastVertexIndex) { + $shuffledGraph[ $permutation->[$i] ][ $permutation->[$j] ] = $other->edgeWeight($i, $j); + } + } + return 1 if $self->isEqual($self->new(\@shuffledGraph)); + } + + return 0; +} + +sub description { + my ($self, %options) = @_; + + my $description = $main::PG->maketext('A graph with vertices [_1].', $self->labelsString); + + my $comma = $main::PG->maketext(', '); + + my @edgeText; + for my $i (0 .. $self->lastVertexIndex) { + for my $j ($i + 1 .. $self->lastVertexIndex) { + next unless $self->hasEdge($i, $j); + push( + @edgeText, + $options{includeWeights} + ? $main::PG->maketext( + '[_1] and [_2] with weight [_3]', $self->vertexLabel($i), + $self->vertexLabel($j), $self->edgeWeight($i, $j) + ) + : $main::PG->maketext('[_1] and [_2]', $self->vertexLabel($i), $self->vertexLabel($j)) + ); + } + } + if (@edgeText == 1) { + $description .= $main::PG->maketext(" There is an edge between [_1].", $edgeText[0]); + } elsif (@edgeText == 2) { + $description .= $main::PG->maketext(" There are edges between [_1] and [_2].", $edgeText[0], $edgeText[1]); + } elsif (@edgeText) { + $description .= $main::PG->maketext( + ' There are edges between [_1][_2]and [_3].', + join($comma, @edgeText[ 0 .. $#edgeText - 1 ]), + $comma, $edgeText[-1] + ); + } + + return $description; +} + +sub image { + my ($self, %options) = @_; + + return $self->gridLayoutImage(%options) if $self->{gridLayout}; + if ($self->{bipartiteLayout}) { + return $self->bipartiteLayoutImage(%options) if ref $self->{bipartiteLayout} eq 'ARRAY'; + # Attempt to partition the graph into two sets in which no edge connects vertices in the same set. + # If not found, then fall through and use the default circle layout. + my ($top, $bottom) = $self->bipartitePartition; + if (ref $top eq 'ARRAY' && ref $bottom eq 'ARRAY' && @$top && @$bottom) { + $self->{bipartiteLayout} = [ $top, $bottom ]; + return $self->bipartiteLayoutImage(%options); + } + } + return $self->wheelLayoutImage(%options) if defined $self->{wheelLayout}; + + my %graphOptions; + @graphOptions{qw(showLabels showWeights)} = delete @options{qw(showLabels showWeights)}; + $graphOptions{showLabels} //= 1; + + my $plot = main::Plot( + xmin => -1.5, + xmax => 1.5, + ymin => -1.5, + ymax => 1.5, + xlabel => '', + ylabel => '', + xvisible => 0, + yvisible => 0, + show_grid => 0, + width => 250, + aspect_ratio => 1, + %options + ); + + my $gap = 2 * $main::PI / ($self->numVertices || 1); + + for my $i (0 .. $self->lastVertexIndex) { + my $iVertex = [ cos($i * $gap), sin($i * $gap) ]; + $plot->add_point(@$iVertex, color => 'blue', mark_size => 3); + + $plot->add_label( + $iVertex->[0], $iVertex->[1], "\\\\($self->{labels}[$i]\\\\)", + color => 'blue', + anchor => 180 + $i * $gap * 180 / $main::PI, + padding => 8 + ) if $graphOptions{showLabels}; + + my $u = 0.275; + my $v = 1 - $u; + + for my $j ($i + 1 .. $self->lastVertexIndex) { + if ($self->hasEdge($i, $j)) { + my $jVertex = [ cos($j * $gap), sin($j * $gap) ]; + $plot->add_dataset($iVertex, $jVertex, color => 'black'); + + if ($graphOptions{showWeights}) { + my @vector = ($jVertex->[0] - $iVertex->[0], $jVertex->[1] - $iVertex->[1]); + my $norm = sqrt($vector[0]**2 + $vector[1]**2); + my @perp = ($vector[1] / $norm, -$vector[0] / $norm); + $plot->add_label( + $u * $iVertex->[0] + $v * $jVertex->[0] + $perp[0] * 0.06, + $u * $iVertex->[1] + $v * $jVertex->[1] + $perp[1] * 0.06, + "\\\\($self->{adjacencyMatrix}->[$i][$j]\\\\)", + color => 'FireBrick', + rotate => ($perp[0] < 0 ? 1 : -1) * + atan2(sqrt(1 - $perp[1] * $perp[1]), $perp[1]) * 180 / + $main::PI - ($perp[1] < 0 ? 180 : 0) + ); + } + } + } + } + + return $plot; +} + +sub gridLayoutImage { + my ($self, %options) = @_; + + die 'Grid layout is not defined, or is but does not have a row and column dimension.' + unless ref $self->{gridLayout} eq 'ARRAY' && @{ $self->{gridLayout} } == 2; + + my %graphOptions; + @graphOptions{qw(showLabels showWeights)} = delete @options{qw(showLabels showWeights)}; + $graphOptions{showLabels} //= 1; + + my $gridGap = 20; + my $gridShift = $gridGap / 2; + my $labelShift = $gridGap / 15; + + my $plot = main::Plot( + xmin => -$gridShift, + xmax => $self->{gridLayout}[1] * $gridGap - $gridShift, + ymin => -$gridShift, + ymax => $self->{gridLayout}[0] * $gridGap - $gridShift, + width => 7 * ($self->{gridLayout}[1] - 1) * $gridGap, + aspect_ratio => 1, + xlabel => '', + ylabel => '', + xvisible => 0, + yvisible => 0, + show_grid => 0, + %options + ); + + for my $i (0 .. $self->{gridLayout}[0] - 1) { + for my $j (0 .. $self->{gridLayout}[1] - 1) { + my $x = $gridGap * $j; + my $y = $gridGap * ($self->{gridLayout}[0] - $i - 1); + $plot->add_point($x, $y, color => 'blue', mark_size => 3); + $plot->add_label( + $x, $y, "\\\\($self->{labels}[$i + $self->{gridLayout}[0] * $j]\\\\)", + color => 'blue', + anchor => -atan2(2, 1) * 180 / $main::PI, + padding => 8, + ) if $graphOptions{showLabels}; + } + } + + my $u = 0.6666; + my $v = 1 - $u; + + for my $i (0 .. $self->lastVertexIndex) { + my $iVertex = [ + int($i / $self->{gridLayout}[0]) * $gridGap, + ($self->{gridLayout}[0] - ($i % $self->{gridLayout}[0]) - 1) * $gridGap + ]; + for my $j ($i + 1 .. $self->lastVertexIndex) { + if ($self->hasEdge($i, $j)) { + my $jVertex = [ + int($j / $self->{gridLayout}[0]) * $gridGap, + ($self->{gridLayout}[0] - ($j % $self->{gridLayout}[0]) - 1) * $gridGap + ]; + $plot->add_dataset($iVertex, $jVertex, color => 'black', width => 1); + if ($graphOptions{showWeights}) { + my $vector = [ $jVertex->[0] - $iVertex->[0], $jVertex->[1] - $iVertex->[1] ]; + $plot->add_label( + $u * $iVertex->[0] + $v * $jVertex->[0], + $u * $iVertex->[1] + $v * $jVertex->[1], + "\\\\($self->{adjacencyMatrix}[$i][$j]\\\\)", + color => 'FireBrick', + anchor => atan2(-$vector->[0], $vector->[1]) * 180 / $main::PI + ); + } + } + } + } + + return $plot; +} + +sub bipartiteLayoutImage { + my ($self, %options) = @_; + + my ($top, $bottom); + + if (ref $self->{bipartiteLayout} eq 'ARRAY' + && @{ $self->{bipartiteLayout} } == 2 + && !grep { ref $_ ne 'ARRAY' } @{ $self->{bipartiteLayout} }) + { + ($top, $bottom) = @{ $self->{bipartiteLayout} }; + } elsif ($self->{bipartiteLayout}) { + ($top, $bottom) = $self->bipartitePartition; + die 'Graph is not bipartite.' unless ref $top eq 'ARRAY' && ref $bottom eq 'ARRAY' && @$top && @$bottom; + } else { + die 'Bipartite layout is not defined.'; + } + + my %graphOptions; + @graphOptions{qw(showLabels showWeights)} = delete @options{qw(showLabels showWeights)}; + $graphOptions{showLabels} //= 1; + + my ($low, $high, $width) = (0, 15, 20); + my @shift = (0, 0); + + my $diff = @$top - @$bottom; + + my $x_max; + if ($diff == 0) { + $x_max = @$top * $width - 10; + } elsif ($diff % 2 == 0 && $diff > 0) { + $x_max = @$top * $width - 10; + $shift[1] += $width * $diff / 2; + } elsif ($diff % 2 == 0) { + $x_max = @$bottom * $width - 10; + $shift[0] += -$width * $diff / 2; + } elsif ($diff > 0) { + $x_max = @$top * $width - 10; + $shift[1] += ($width / 2) * $diff; + } else { + $x_max = @$bottom * $width - 10; + $shift[0] += (-$width / 2) * $diff; + } + + my $plot = main::Plot( + xmin => -10, + xmax => $x_max, + ymin => -5, + ymax => 20, + width => 250, + aspect_ratio => 1, + xlabel => '', + ylabel => '', + xvisible => 0, + yvisible => 0, + show_grid => 0, + %options + ); + + for my $i (0 .. $#$top) { + $plot->add_point($i * $width + $shift[0], $high, color => 'blue', mark_size => 3); + $plot->add_label( + $i * $width + $shift[0], $high, "\\\\($self->{labels}[$top->[$i]]\\\\)", + color => 'blue', + h_align => 'center', + v_align => 'bottom', + padding => 8 + ) if $graphOptions{showLabels}; + } + for my $j (0 .. $#$bottom) { + $plot->add_point($j * $width + $shift[1], $low, color => 'blue', mark_size => 3); + $plot->add_label( + $j * $width + $shift[1], $low, "\\\\($self->{labels}[$bottom->[$j]]\\\\)", + color => 'blue', + h_align => 'center', + v_align => 'top', + padding => 8 + ) if $graphOptions{showLabels}; + } + + my ($u, $v) = $diff >= 0 ? (2 / 3, 1 / 3) : (1 / 3, 2 / 3); + + for my $i (0 .. $#$top) { + for my $j (0 .. $#$bottom) { + next unless $self->hasEdge($top->[$i], $bottom->[$j]); + my $point1 = [ $i * $width + $shift[0], $high ]; + my $point2 = [ $j * $width + $shift[1], $low ]; + $plot->add_dataset($point1, $point2, color => 'black'); + if ($graphOptions{showWeights}) { + my $vector = [ $point2->[0] - $point1->[0], $point2->[1] - $point1->[1] ]; + $plot->add_label( + $u * $point1->[0] + $v * $point2->[0], + $u * $point1->[1] + $v * $point2->[1], + "\\\\($self->{adjacencyMatrix}[ $top->[$i] ][ $bottom->[$j] ]\\\\)", + color => 'FireBrick', + anchor => atan2($vector->[0], -$vector->[1]) * 180 / $main::PI + 180, + padding => 2 + ); + } + } + } + + return $plot; +} + +sub wheelLayoutImage { + my ($self, %options) = @_; + + die 'Wheel layout is not defined.' unless defined $self->{wheelLayout}; + + my %graphOptions; + @graphOptions{qw(showLabels showWeights)} = delete @options{qw(showLabels showWeights)}; + $graphOptions{showLabels} //= 1; + + my $plot = main::Plot( + xmin => -1.5, + xmax => 1.5, + ymin => -1.5, + ymax => 1.5, + width => 250, + aspect_ratio => 1, + xlabel => '', + ylabel => '', + xvisible => 0, + yvisible => 0, + show_grid => 0, + %options + ); + + my $gap = 2 * $main::PI / ($self->lastVertexIndex || 1); + + $plot->add_point(0, 0, color => 'blue', mark_size => 3); + $plot->add_label( + 0, 0, "\\\\($self->{labels}[ $self->{wheelLayout} ]\\\\)", + color => 'blue', + anchor => 180 + $gap * 90 / $main::PI, + padding => 10 + ) if $graphOptions{showLabels}; + + for my $i (0 .. $self->lastVertexIndex) { + next if $i == $self->{wheelLayout}; + + my $iRel = $i > $self->{wheelLayout} ? $i - 1 : $i; + + my $iVertex = [ cos($iRel * $gap), sin($iRel * $gap) ]; + $plot->add_point(@$iVertex, color => 'blue', mark_size => 3); + + $plot->add_label( + $iVertex->[0], $iVertex->[1], "\\\\($self->{labels}[$i]\\\\)", + color => 'blue', + anchor => 180 + $iRel * $gap * 180 / $main::PI, + padding => 8 + ) if $graphOptions{showLabels}; + + if ($self->hasEdge($self->{wheelLayout}, $i)) { + $plot->add_dataset([ 0, 0 ], $iVertex, color => 'black'); + if ($graphOptions{showWeights}) { + my $norm = sqrt($iVertex->[0]**2 + $iVertex->[1]**2); + my @perp = ($iVertex->[1] / $norm, -$iVertex->[0] / $norm); + $plot->add_label( + 0.5 * $iVertex->[0] + $iVertex->[1] / $norm * 0.1, + 0.5 * $iVertex->[1] - $iVertex->[0] / $norm * 0.1, + "\\\\($self->{adjacencyMatrix}->[ $self->{wheelLayout} ][$i]\\\\)", + color => 'FireBrick', + rotate => ($perp[0] < 0 ? 1 : -1) * + atan2(sqrt(1 - $perp[1] * $perp[1]), $perp[1]) * 180 / + $main::PI - ($perp[1] < 0 ? 180 : 0) + ); + } + } + + for my $j ($i + 1 .. $self->lastVertexIndex) { + next if $j == $self->{wheelLayout}; + + my $jRel = $j > $self->{wheelLayout} ? $j - 1 : $j; + + if ($self->hasEdge($i, $j)) { + my $jVertex = [ cos($jRel * $gap), sin($jRel * $gap) ]; + $plot->add_dataset($iVertex, $jVertex, color => 'black'); + + if ($graphOptions{showWeights}) { + my @vector = ($jVertex->[0] - $iVertex->[0], $jVertex->[1] - $iVertex->[1]); + my $norm = sqrt($vector[0]**2 + $vector[1]**2); + my @perp = ($vector[1] / $norm, -$vector[0] / $norm); + $plot->add_label( + 0.5 * $iVertex->[0] + 0.5 * $jVertex->[0] + $vector[1] / $norm * 0.1, + 0.5 * $iVertex->[1] + 0.5 * $jVertex->[1] - $vector[0] / $norm * 0.1, + "\\\\($self->{adjacencyMatrix}->[$i][$j]\\\\)", + color => 'FireBrick', + rotate => ($perp[0] < 0 ? 1 : -1) * + atan2(sqrt(1 - $perp[1] * $perp[1]), $perp[1]) * 180 / + $main::PI - ($perp[1] < 0 ? 180 : 0) + ); + } + } + } + } + + return $plot; +} + +sub copy { + my $self = shift; + return $self->new($self); +} + +sub shuffle { + my ($self, $permuteLabels) = @_; + my @shuffledGraph; + my @vertexPermutation = main::random_subset($self->numVertices, 0 .. $self->lastVertexIndex); + my @inverseVertexPermutation; # Only needed if labels are also permuted. + @inverseVertexPermutation[@vertexPermutation] = 0 .. $#vertexPermutation; + for my $i (0 .. $self->lastVertexIndex) { + for my $j (0 .. $self->lastVertexIndex) { + $shuffledGraph[ $vertexPermutation[$i] ][ $vertexPermutation[$j] ] = $self->edgeWeight($i, $j); + } + } + return $self->new( + \@shuffledGraph, + labels => $permuteLabels ? [ @{ $self->{labels} }[@inverseVertexPermutation] ] : $self->{labels}, + ref $self->{bipartiteLayout} eq 'ARRAY' ? () : (bipartiteLayout => $self->{bipartiteLayout}), + defined $self->{wheelLayout} ? (wheelLayout => $vertexPermutation[ $self->{wheelLayout} ]) : () + ); +} + +sub nearestNeighborPath { + my ($self, $vertex) = @_; + + my @visited = (undef) x $self->numVertices; + $visited[$vertex] = 1; + + my @path = ($vertex); + my $weight = 0; + my $currentVertex = $vertex; + + while (@path < $self->numVertices) { + my $nearest; + my $min = 0; + for my $i (0 .. $self->lastVertexIndex) { + next if $i == $currentVertex || defined $visited[$i] || !$self->hasEdge($currentVertex, $i); + if ($min == 0 || $self->edgeWeight($currentVertex, $i) < $min) { + $min = $self->edgeWeight($currentVertex, $i); + $nearest = $i; + } + } + last unless defined $nearest; + push @path, $nearest; + $visited[$nearest] = 1; + $weight += $self->edgeWeight($currentVertex, $nearest); + $currentVertex = $nearest; + } + + if ($self->hasEdge($currentVertex, $vertex)) { + push @path, $vertex; + $weight += $self->edgeWeight($currentVertex, $vertex); + } + + return (\@path, $weight); +} + +sub kruskalGraph { + my $self = shift; + + my $numComponents = $self->numComponents; + my $graph = $self->copy; + my $tree = GraphTheory::SimpleGraph->new($graph->numVertices, labels => $graph->labels); + my $numTreeComponents = $tree->numVertices; + + my @treeWeights; + my $treeWeight = 0; + my @algorithmSteps; + + my @sortedEdges; + for my $i (0 .. $self->lastVertexIndex) { + for my $j ($i + 1 .. $self->lastVertexIndex) { + next unless $self->hasEdge($i, $j); + push @sortedEdges, [ $i, $j, $self->edgeWeight($i, $j) ]; + } + } + @sortedEdges = main::PGsort(sub { $_[0][-1] < $_[1][-1] }, @sortedEdges); + + while (@sortedEdges && $numTreeComponents > $numComponents) { + my $edge = shift @sortedEdges; + my $weight = $edge->[2]; + + $graph->removeEdge($edge->[0], $edge->[1]); + $tree->addEdge(@$edge); + my $currentTreeNumComponents = $tree->numComponents; + if ($currentTreeNumComponents < $numTreeComponents) { + push @algorithmSteps, [ @$edge, 1 ]; + $numTreeComponents = $currentTreeNumComponents; + $treeWeight += $weight; + push @treeWeights, $weight; + } else { + push @algorithmSteps, [ @$edge, 0 ]; + $tree->removeEdge($edge->[0], $edge->[1]); + } + } + + return ($tree, $treeWeight, \@treeWeights, \@algorithmSteps); +} + +sub hasEulerCircuit { + my $self = shift; + + return wantarray ? (0, $main::PG->maketext('This graph is not connected.')) : 0 if $self->numComponents != 1; + + for my $degree ($self->degrees) { + return wantarray ? (0, $main::PG->maketext('The degrees of the vertices in this graph are not all even.')) : 0 + if $degree % 2 != 0; + } + + return 1; +} + +sub hasEulerTrail { + my $self = shift; + + return wantarray ? (0, $main::PG->maketext('This graph is not connected.')) : 0 if $self->numComponents != 1; + + my ($odd, $even) = (0, 0); + + for my $degree ($self->degrees) { + if ($degree % 2 == 0) { ++$even } + else { ++$odd } + } + + return wantarray + ? ( + 0, + $main::PG->maketext( + 'This graph does not have two vertices of odd degree and all other vertices of even degree.') + ) + : 0 + if $even != $self->numVertices && $odd != 2; + return 1; +} + +sub pathIsEulerTrail { + my ($self, @path) = @_; + + my $graph = $self->copy; + + my $i = shift @path; + do { + my $j = shift @path; + return + wantarray ? (0, $main::PG->maketext('An edge traversed by this path does not exist in the graph.')) : 0 + unless $graph->hasEdge($i, $j); + $graph->removeEdge($i, $j); + $i = $j; + } while @path > 0; + + for my $i (0 .. $graph->lastVertexIndex) { + for my $j (0 .. $i - 1) { + return wantarray ? (0, $main::PG->maketext('This path does not traverse all edges.')) : 0 + if $graph->hasEdge($i, $j); + } + } + + return 1; +} + +sub pathIsEulerCircuit { + my ($self, @path) = @_; + my @isEulerTrail = $self->pathIsEulerTrail(@path); + return wantarray ? @isEulerTrail : 0 unless $isEulerTrail[0]; + return wantarray ? (0, $main::PG->maketext('This path is not a circuit.')) : 0 unless $path[0] == $path[-1]; + return 1; +} + +sub hasCircuit { + my $self = shift; + + my $graph = $self->copy; + + for (my $i = 0; $i < $graph->numVertices; ++$i) { + my $sum = 0; + for my $j (0 .. $graph->lastVertexIndex) { + $sum += $graph->hasEdge($i, $j) ? 1 : 0; + } + if ($sum == 1) { + for my $j (0 .. $graph->lastVertexIndex) { + $graph->removeEdge($i, $j); + } + $i = -1; + } + } + + for my $i (0 .. $graph->lastVertexIndex) { + for my $j ($i + 1 .. $graph->lastVertexIndex) { + return 1 if $graph->hasEdge($i, $j); + } + } + + return 0; +} + +sub isTree { + my $self = shift; + return wantarray ? (0, $main::PG->maketext('This graph has a circuit.')) : 0 if $self->hasCircuit; + return wantarray ? (0, $main::PG->maketext('This graph is not connected.')) : 0 if $self->numComponents > 1; + return 1; +} + +sub isForest { + my $self = shift; + return wantarray ? (0, $main::PG->maketext('This graph has a circuit.')) : 0 if $self->hasCircuit; + return 1; +} + +# Returns 1 if the given $graph is bipartite, and 0 otherwise. +sub isBipartite { + my $self = shift; + + my @vertexColors = (0) x $self->numVertices; + my @color = (1, 2); + + my $done; + do { + my $i = 0; + while ($vertexColors[$i] != 0) { ++$i; } + $vertexColors[$i] = $color[0]; + + my @verticesToClassify; + push @verticesToClassify, $i; + + do { + my $vertex = shift @verticesToClassify; + my $currentColor = $vertexColors[$vertex]; + for my $i (0 .. $self->lastVertexIndex) { + if ($self->hasEdge($vertex, $i)) { + return 0 if $currentColor == $vertexColors[$i]; + if ($vertexColors[$i] == 0) { + push @verticesToClassify, $i; + $vertexColors[$i] = $color[ 2 - $currentColor ]; + } + } + } + } while @verticesToClassify; + + $done = 1; + for my $i (0 .. $self->lastVertexIndex) { + $done *= $vertexColors[$i]; + } + } while $done == 0; + + return 1; +} + +sub bipartitePartition { + my $self = shift; + + my @partition = ([], []); + + my %partitionAssignments; + + my $done; + do { + my $i = 0; + ++$i while defined $partitionAssignments{$i}; + $partitionAssignments{$i} = 0; + push(@{ $partition[0] }, $i); + + my @vertices; + push @vertices, $i; + + do { + my $vertex = shift @vertices; + my $currentPartition = $partitionAssignments{$vertex}; + for my $i (0 .. $self->lastVertexIndex) { + if ($self->hasEdge($vertex, $i)) { + return if defined $partitionAssignments{$i} && $currentPartition == $partitionAssignments{$i}; + if (!defined $partitionAssignments{$i}) { + push @vertices, $i; + $partitionAssignments{$i} = $currentPartition ? 0 : 1; + push(@{ $partition[ $partitionAssignments{$i} ] }, $i); + } + } + } + } while @vertices; + + $done = 1; + for my $i (0 .. $self->lastVertexIndex) { + unless (defined $partitionAssignments{$i}) { + $done = 0; + last; + } + } + } while !$done; + + $partition[0] = [ main::num_sort(@{ $partition[0] }) ]; + $partition[1] = [ main::num_sort(@{ $partition[1] }) ]; + + return @partition; +} + +sub dijkstraPath { + my ($self, $start, $end) = @_; + + my ($ind, $new, @dist, @path, @prev, @used); + + for my $i (0 .. $self->lastVertexIndex) { + $dist[$i] = -1; + $prev[$i] = -1; + } + $dist[$start] = 0; + + my @available = (0 .. $self->lastVertexIndex); + + while (@available) { + my $min = 1000000000; # Infinity (for all practical purposes) + for my $i (0 .. $#available) { + my $loc = $available[$i]; + if ($dist[$loc] >= 0 && $dist[$loc] < $min) { + $new = $loc; + $ind = $i; + $min = $dist[$new]; + } + } + push @used, $new; + splice @available, $ind, 1; + @used = main::num_sort(@used); + + for my $i (0 .. $self->lastVertexIndex) { + my $weight = $self->edgeWeight($new, $i); + if ($weight != 0) { + if ($dist[$i] > $min + $weight || $dist[$i] < 0) { + $dist[$i] = $min + $weight; + $prev[$i] = $new; + } + } + } + } + + my $loc = $end; + while ($loc != $start) { + unshift @path, $loc; + $loc = $prev[$loc]; + } + unshift @path, $start; + + return ($dist[$end], @path); +} + +sub sortedEdgesPath { + my $self = shift; + + my @sortedEdges; + my $sortedGraph = GraphTheory::SimpleGraph->new($self->numVertices, labels => $self->labels); + + for my $i (0 .. $self->lastVertexIndex) { + for my $j ($i + 1 .. $self->lastVertexIndex) { + next unless $self->hasEdge($i, $j); + push @sortedEdges, [ $i, $j, $self->edgeWeight($i, $j) ]; + } + } + + @sortedEdges = main::PGsort(sub { $_[0][-1] < $_[1][-1] }, @sortedEdges); + + # Returns 0 if an edge can be added to the sorted edges based graph, 1 if adding the edge results in a vertex having + # more than two edges connected to it, and 2 if adding the edge results in the path having a circuit (unless it is + # the last vertex in which case that is okay since it completes the circuit). + my $edgeCheck = sub { + my $graph = shift; + + my $sum = 0; + + for my $i (0 .. $graph->lastVertexIndex) { + my $degree = $graph->vertexDegree($i); + return 1 if $degree > 2; + $sum += $degree; + } + + return $sum < 2 * $graph->numVertices && $graph->hasCircuit ? 2 : 0; + }; + + my @pathWeights; + my @algorithmSteps; + + do { + my $edge = shift @sortedEdges; + $sortedGraph->addEdge(@$edge); + my $edgeCheckResult = $edgeCheck->($sortedGraph); + push @algorithmSteps, [ @$edge, $edgeCheckResult ]; + if ($edgeCheckResult) { + $sortedGraph->removeEdge(@$edge); + } else { + push @pathWeights, $edge->[-1]; + } + } while @pathWeights < $sortedGraph->numVertices && @sortedEdges; + + return (\@pathWeights, \@algorithmSteps, $sortedGraph); +} + +sub chromaticNumber { + my $self = shift; + return Chromatic::computeBestColoring(@{ $self->{adjacencyMatrix} }); +} + +sub kColoring { + my ($self, $k) = @_; + + my @colors = (-1) x $self->numVertices; + my @nextColor = (0) x $self->numVertices; + my $v = 0; + + while ($v >= 0) { + my $assigned = 0; + while ($nextColor[$v] < $k) { + my $c = ++$nextColor[$v]; + my $ok = 1; + for my $u (0 .. $self->lastVertexIndex) { + if ($self->hasEdge($v, $u) && $colors[$u] == $c) { + $ok = 0; + last; + } + } + if ($ok) { + $colors[$v] = $c; + ++$v; + return @colors if $v == $self->numVertices; + $nextColor[$v] = 0; + $assigned = 1; + last; + } + } + unless ($assigned) { + $colors[$v] = -1; + $nextColor[$v] = 0; + --$v; + } + } + + return; +} + +# The GraphTheory::SimpleGraph::Parser::List, GraphTheory::SimpleGraph::EdgeSet and GraphTheory::SimpleGraph::Edge +# packages are special lists for the edgeSet return value context. + +package GraphTheory::SimpleGraph::Parser::List; +our @ISA = qw(Parser::List::List); + +sub _check { + my $self = shift; + $self->SUPER::_check; + + # Only handle implicit lists or lists explicitly opened with a brace. + return if $self->{open} && $self->{open} ne '{'; + + my $entryType = $self->typeRef->{entryType}; + + # Since there can only be one brace paren, the distinction between an edge and an edge set needs to be made here. + # An empty list or a list that contains another list is an edge set. All other lists are edges. + my $isEdgeSet = $self->length ? 0 : 1; + for (@{ $self->{coords} }) { + if ($_->{type}{list} && $_->{type}{list} == 1) { $isEdgeSet = 1; last; } + } + + if ($isEdgeSet) { + $self->{type} = Value::Type('EdgeSet', scalar(@{ $self->{coords} }), $entryType, list => 1); + } elsif ($self->{type}{name} ne 'Edge') { + $self->{type} = Value::Type('Edge', scalar(@{ $self->{coords} }), $entryType, list => 1); + } + + if ($self->{type}{name} eq 'Edge') { + my $strings = $self->context->strings->all; + for (@{ $self->{coords} }) { + $self->Error('An edge may only contain vertices.') + unless ref $_ eq 'Parser::String' + && defined $strings->{ $_->{value} } + && $strings->{ $_->{value} }{isVertex}; + } + $self->Error('An edge must contain exactly two vertices.') if $self->length != 2; + } elsif ($self->{type}{name} eq 'EdgeSet') { + for (@{ $self->{coords} }) { + $self->Error('An edge set may only contain edges.') unless $_->{type}{name} eq 'Edge'; + } + } +} + +package GraphTheory::SimpleGraph::Value::EdgeSet; +our @ISA = qw(Value::List); + +sub cmp_defaults { + my ($self, %options) = @_; + return ( + $self->SUPER::cmp_defaults(%options), + entry_type => 'edge', + list_type => 'edge set', + removeParens => 0, + showParenHints => 1, + implicitList => 0 + ); +} + +sub compare { + my ($l, $r, $flag) = @_; + my $self = $l; + $r = $self->promote($r); + if ($flag) { my $tmp = $l; $l = $r; $r = $tmp } + my @l = main::num_sort($l->value); + my @r = main::num_sort($r->value); + while (@l && @r) { + my $cmp = shift(@l) <=> shift(@r); + return $cmp if $cmp; + } + return @l - @r; +} + +package GraphTheory::SimpleGraph::Value::Edge; +our @ISA = qw(Value::List); + +sub cmp_defaults { + my ($self, %options) = @_; + return ( + $self->SUPER::cmp_defaults(%options), + entry_type => 'vertex', + list_type => 'edge', + removeParens => 0, + showParenHints => 1, + implicitList => 0 + ); +} + +sub compare { + my ($l, $r, $flag) = @_; + my $self = $l; + $r = $self->promote($r); + if ($flag) { my $tmp = $l; $l = $r; $r = $tmp } + my @l = main::num_sort($l->value); + my @r = main::num_sort($r->value); + while (@l && @r) { + my $cmp = shift(@l) <=> shift(@r); + return $cmp if $cmp; + } + return @l - @r; +} + +1; + +=head1 NAME + +SimpleGraph.pl - Tools for displaying and manipulating simple graphs from graph +theory. + +=head1 DESCRIPTION + +The core of this macro is the C object which +represents simple graphs from graph theory via an adjacency matrix. + +=head1 FUNCTIONS + +The following functions can be used to construct a C +object. + +=head2 SimpleGraph + + $graph = SimpleGraph($definition, %options); + +This is an alias for the C constructor. + +The C<$definition> argument is required and can be the number of vertices in the +graph, another C object, a reference to an array of +arrays of numbers, a reference to a MathObject C, a reference to an +array of arrays containing two strings and possibly a number (an edge set with +optional edge weights), a reference to an array of +C objects, or a reference to a +C object. + +If C<$definition> is the number of vertices in the graph, then the graph that is +returned will have that number of vertices and no edges. Note that the number of +vertices must be greater than 0. + +If C<$definition> is another C object, then a copy of +the graph represented by that object will be returned. + +If C<$definition> is a one of the matrix forms (a reference to an array of +arrays of numbers or a reference to a MathObject C), then that matrix +will be used for the adjacency matrix of the graph. Note that it must be a +square symmetric matrix with zero entries along the diagonal and must have size +greater than or equal to 1. For example, + + [ + [ 0, 1, 0], + [ 1, 0, 2], + [ 0, 2, 0] + ] + +represents a graph that has three vertices, an edge between the first and +second vertices with weight one, and an edge between the second and third +vertices with weight 2. + +If C<$definition> is one of the edge set definition forms (a reference to an +array of arrays containing two strings and possibly a number, a reference to an +array of C objects, or a reference to a +C object), then the C option +is required. For the reference to an array of arrays form each edge in the edge +set must be an array containing two strings and possibly a third numeric +element. The two strings are the labels for the vertices connected by the edge. +The optional third element is the weight of the edge. If the optional third +element is not given, then the edge will have a weight of one. Note that all +labels provided in the C option will be used with this definition, and +will all be vertices in the graph. For example, if C<$definition> is given as + + [ + [ 'A', 'B' ], + [ 'A', 'C', 2 ], + [ 'B', 'C', 3 ], + ] + +and the C option is C<['A', 'B', 'C', 'D']>, then the return object will +represent a graph that has at four vertices labeled 'A', 'B', 'C', and 'D', +respectively, an edge between 'A' and 'B' with weight one, an edge between 'A' +and 'C' with weight 2, an edge between 'B' and 'C' with weight 3, and no edges +connecting to the vertex 'D'. Note that setting weights when a graph is +constructed via a reference to an array of C +objects or a reference to a C object is not +supported. However, weights can still be set after construction with the +L method or the L method. + +The arguments that may be passed in C<%options> are as follows. Note that all of +these options are undefined by default. + +=over + +=item labels + +The value of this option must be a reference to an array of strings. These are +the labels for the vertices of the graph. The array must contain at least as +many strings as there are vertices of the graph. Any extraneous strings will not +be used (except in the edge set definition case as mentioned above). If this +option is not given, then a random set of consecutive letters from the alphabet +will be used for the labels of the vertices. Note that if an edge set version of +the C<$definition> argument is used, then this option must be provided. + +=item gridLayout + +If this option is given, then it must be a reference to an array containing two +numbers, for example, C<[3, 4]>. In this case when the L method is +called to create the image of the graph, the vertices will be arranged into a +grid with the number of rows equal to the first number in the array, and the +number of columns equal to the second number in the array. Note that many graphs +will not work well in a grid layout. This is primarily intended for graphs +created via the L function where its first argument is used +as the value for this option. This option is generally intended for internal use +in this macro specifically for that function. However, there may be other graphs +that may also work well in a grid layout. + +=item bipartiteLayout + +If this option is given, then it must either be 1 or a reference to an array +containing two arrays of vertex indices. If this is 1, then a partition of the +vertices into two sets in which no two vertices in the same set are connected by +an edge will attempt to be obtained internally via the L +method. If the graph is not bipartite then no such partition will be found and +the L method will not use the bipartite layout. Instead the image will +be displayed in the default circle layout. If this is an array containing two +arrays of vertex indices, then the two arrays must be the partition of the +vertices of the graph in which no two vertices in the same set are connected by +an edge. In this case when the L method is called to create the image of +the graph, the vertices will be arranged into to rows with the vertices in the +first set in the top row, and the vertices in the second set in the bottom row. +Note that an exception will be thrown if this option is 1 and the graph is not +bipartite. + +=item wheelLayout + +If this option is given, then it must be an index of one of the vertices in the +graph (0, 1, 2, ...), and when the L method is called to create the image +of the graph, that vertex will be placed in the center of a circle, and the +other vertices will be evenly spaced around the perimeter of the circle. + +=back + +=head2 randomSimpleGraph + + $graph = randomSimpleGraph($size, %options); + +This function returns a C object in which the +existence of an edge between any two vertices is randomly determined. + +The C<$size> argument is required and must either be an integer that is greater +than zero, or a reference to an array containing two integers, both of which are +greater than zero. If this argument is an integer that is greater than zero, +then that will be the number of vertices of the graph. If this argument is a +reference to an array containing two integers that are greater than zero, then +the number of vertices in the graph will be the product of those two integers. +Furthermore, this array reference will be passed as the C option to +the C constructor, and the image of the graph will +have the vertices arranged into rows and columns. Furthermore, in the graph will +be generated such that the only possible edges will connect vertices that are +adjacent in that grid (including diagonal adjacency). + +The arguments that may be passed in C<%options> are as follows. + +=over + +=item labels + +The value of this option, if given, must be a reference to an array of strings. +These are the labels for the vertices of the graph. The array must contain at +least as many strings as there are vertices of the graph. Any extraneous strings +will not be used (except in the edge set definition case as mentioned above). If +this option is not given, then a random set of consecutive letters from the +alphabet will be used for the labels of the vertices. + +=item edgeProbability + +This is the probability that there will be an edge between any two vertices. By +default this is 0.5. Note that if C<$size> is a reference to an array of +integers, then this only applies to vertices that are adjacent in the grid, and +there will never be edges between vertices that are not adjacent in the grid. + +=item edgeWeightRange + +If this is given, then it must be a reference to an array of two or three +numbers. In this case the weights of the edges will be a random number from the +first number to the second number with increments of the optional third number +(the increment defaults to 1). In fact the elements of this array are passed +directly to the C function (see L). Note that the C option takes precedence over this +option, and if it is given and is an integer greater than one, then it will be +used instead of this option. Furthermore, if neither this option nor the +C option are given, then all edge weights will be 1. + +=item startEdgeWeight + +If this option is given, then it must be an integer that is greater than zero. +In this case this will be the first edge weight used in the generated graph, and +the other edge weights will be determined by adding increments of the +following C option. + +=item edgeWeightIncrement + +If this option is given, then it must be an integer that is greater than zero. +Note that this is only used if C is also given and is greater +than zero, and in that case increments of this number will be added to the +C and used for the weights of the edges. If the +C is given, is an integer greater than 0, and this option is +undefined, then an edge weight increment of 1 will be used. + +=back + +The examples below demonstrate the usage of this function and its options in +more details. + + $graph = randomSimpleGraph(random(3, 5), edgeProbability => 1); + +will return a complete graph with 3, 4, or 5 vertices and all edge weights equal +to 1. + + $graph = randomSimpleGraph(random(3, 5), edgeProbability => 0); + +will return a graph with 3, 4, or 5 vertices that has no edges. Note that +C<$graph = SimpleGraph(random(3, 5))> is equivalent and is slightly more +efficient. + + $graph = randomSimpleGraph( + 5, + edgeProbability => 0.3, + edgeWeightRange => [1, 10] + ); + +will return a graph with 5 vertices with probability 0.3 of an edge between any +two vertices, and random edge weights from 1 to 10 (in increments of 1). + + $graph = randomSimpleGraph( + 5, + startEdgeWeight => 2, + edgeWeightIncrement => 3 + ); + +will return a graph with 5 vertices with probability 0.5 of an edge between any +two vertices, and the edge weights will be 2, 5, 8, 11, ... (which will be +randomly assigned to the edges in the graph). + + $graph = randomSimpleGraph([3, 4], edgeProbability => 0.6); + +will return a graph with 12 vertices and all edge weights equal to 1. When the +image of this graph is displayed the vertices will be arranged into a grid with +3 rows and 4 columns. The probability of an edge between vertices that are +adjacent in the grid is 0.6. There will be no edges between vertices that are +not adjacent in the grid. + +=head2 randomGraphWithEulerCircuit + + $graph = randomGraphWithEulerCircuit($size, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that has an Euler circuit. This function +also accepts the C, C, C, and +C options accepted by the L function. + +=head2 randomGraphWithEulerTrail + + $graph = randomGraphWithEulerTrail($size, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that has an Euler trail, but does not have +an Euler circuit. This function also accepts the C, C, +C, and C options accepted by the +L function. + +=head2 randomGraphWithoutEulerTrail + + $graph = randomGraphWithoutEulerTrail($size, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that does not have an Euler trail (or +circuit). This function also accepts the C, C, +C, and C options accepted by the +L function. + +=head2 randomBipartiteGraph + + $graph = randomBipartiteGraph($size, %options); + +This function returns a C object that represents a +random bipartite graph. + +The C<$size> argument can either be a number or a reference to an array of two +integers that are greater than zero. If it is a number, then the graph will have +C<$size> vertices, and the number of vertices in the two parts of the bipartite +partition will be randomly determined. If it is a reference to an array of two +integers, then graph will have the number of vertices equal to the sum of those +two numbers, and the two sets in the bipartite partition will have those numbers +of elements. + +This function also accepts the C, C, +C, C, and C options +accepted by the L function. + +=head2 randomTreeGraph + + $graph = randomTreeGraph($size, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that is a tree. This function also accepts +the C, C, C, and +C options accepted by the L function. + +=head2 randomForestGraph + + $graph = randomForestGraph($size, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that is a forest, but is not a tree. This +function also accepts the C, C, C, +and C options accepted by the L function. + +=head2 randomHamiltonianGraph + + $graph = randomHamiltonianGraph($size, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that is Hamiltonian. Note that C<$size> must +be 5 or more, or an exception will be thrown. The C may be passed in +C<%options>, and is the same as for the above functions. + +=head2 randomNonHamiltonianGraph + + $graph = randomNonHamiltonianGraph($size, $type, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that is not Hamiltonian. + +If C<$type> is even, then the graph will have a vertex of degree one. If +C<$type> is odd, then the graph will consist of two cycles joined by a single +edge. Note that for the odd C<$type> graph, the C<$size> must be at least 6, or +an exception will be thrown. + +This function also accepts the C, C, +C, and C options accepted by the +L function. + +=head2 randomGraphWithDegreeSequence + + $graph = randomGraphWithDegreeSequence($degrees, %options); + +This function returns a C object that represents a +random graph that has the given C<$degree> sequence, if such a graph is +possible, and undefined otherwise. + +The C<$degrees> argument must be a reference to an array containing the desired +vertex degrees. + +This function also accepts the C, C, +C, and C options accepted by the +L function. + +=head2 cycleGraph + + $graph = cycleGraph($size, %options); + +This function returns a C object that represents a +graph with C<$size> vertices that is a cycle. This function also accepts the +C, C, C, and C +options accepted by the L function. + +=head2 completeGraph + + $graph = completeGraph($size, %options); + +This function returns a C object that represents a +graph with C<$size> vertices that is a complete graph. This function also +accepts the C, C, C, and +C options accepted by the L function. + +=head2 wheelGraph + + $graph = wheelGraph($size, %options); + +This function returns a C object that represents a +graph with C<$size> vertices that is a wheel. This function also accepts the +C, C, C, and C +options accepted by the L function. + +Note that the returned graph object will have the C graph option +set to the vertex at the hub of the wheel, and that vertex will be displayed in +the center in the graph image. + +=head2 completeBipartiteGraph + + $graph = completeBipartiteGraph($m, $n, %options); + +This function returns a C object that represents a +graph with C<$m * $n> vertices that is a complete bipartite graph with C<$m> +vertices in first set in the bipartite partition, and C<$n> vertices in the +other set in the bipartite partition. This function also accepts the C, +C, C, and C options +accepted by the L function. + +Note that the returned graph object will have the C graph +option set so that the graph image is nicely displayed with the vertices in the +first set in the bipartite partition in a row on the top, and the vertices in +the other set in a row on the bottom. + +=head2 EdgeSet + + $edgeSet = EdgeSet($edges); + $edgeSet = EdgeSet($edge1, $edge2, ...); + +This method is an alias for the C +MathObject constructor. It can be passed a reference to an array of edges or +just a list of edges. An edges can be a reference to an array containing two +vertices, or can be a C MathObject. As +with all MathObject constructors, the first argument can optionally be a +context. Note that vertices must be strings in the current context or the +context that is passed. + +Since this derives from a MathObject C, a C must be used for +a custom checker routine. + +Usually this would only be used in the C context. Otherwise the +returned value will not display correctly in the problem and will not work well +as an answer. However, the object can still be passed to the C +function to create a graph with that edge set. + +=head2 Edge + + $edge = Edge($vertex1, $vertex2); + $edge = EdgeSet([ $vertex1, $vertex2 ]); + +This method is an alias for the C +MathObject constructor. It can be passed a reference to an array containing two +vertices or a list of two vertices. As with all MathObject constructors, the +first argument can optionally be a context. Note that vertices must be strings +in the current context or the context that is passed. + +Since this derives from a MathObject C, a C must be used for +a custom checker routine. + +Usually this would only be used in the C context. Otherwise the +returned value will not display correctly in the problem and will not work well +as an answer. However, the object can still be passed in an array reference to +the C function to create a graph with that edge set. + +=head1 GraphTheory::SimpleGraph Methods + +The C package is the heart of this macro. All of the +functions described above return an object that is an instance of this package +class. See the L function for the constructor usage. In addition, +the following object methods are available. + +=head2 adjacencyMatrix + + $matrix = $graph->adjacencyMatrix; + +This method returns the adjacency matrix that defines the graph. That is a +reference to an array of array references each containing numbers. The matrix +will be a symmetric square matrix with zero entries along the diagonal. The +entry in the i-th row and j-th column will be zero if there is not an edge +connecting the i-th and j-th vertex, and will be nonzero if there is such an +edge. Furthermore, the entry represents the edge weight in the case that it is +not zero. + +Note that indexing of vertices is zero based. So the first vertex has index 0, +and the last vertex has index one less than the number of vertices in the graph. + +=head2 edgeSet + + $edgeSet = $graph->edgeSet(%options); + +This method returns the edge set for the graph as a MathObject C +containing MathObject Cs. For example, if a graph has adjacency matrix + + [ + [ 0, 1, 0, 0 ], + [ 1, 0, 1, 0 ], + [ 0, 1, 0, 1 ] + [ 0, 0, 1, 0 ] + ] + +and labels 'A', 'B', 'C', and 'D', then this method would return + + {{A, B}, {B, C}, {C, D}} + +where the outer set is a MathObject C, the elements of that set are +MathObject Cs, and the elements of those sets are MathObject Cs. +The returned C will be either be in a created C context with +the vertex labels of the graph defined as strings and marked as being vertices +in that context, or in a context provided in the C<%options> as described below. + +This return value can be used as an answer to a question, and will display in +output as shown above. + +Note, that edge weights are not represented in the return value of this method. + +The following options that may be passed in C<%options>. + +=over + +=item context (Default: C<< context => undef >>) + +The context to use for the returned MathObject C object. If this is not +provided, then a C context will be created, the vertex labels added as +strings to the context and marked as being vertices and the returned object will +be in the created context. + +=item caseSensitive (Default: C<< caseSensitive => 1 >>) + +Whether vertex labels that are added to the context are case sensitive or not. + +=back + +=head2 addVerticesToContext + + $graph->addVerticesToContext($caseSensitive, $context); + +This adds the vertex labels of the graph to the context and marks them as being +vertices (by adding the C flag). + +The C<$caseSensitive> and C<$context> arguments are optional. + +If C<$caseSensitive> is 1 or the argument is not provided, then the vertices +will be case sensitive. So entering "a" will not be accepted as correct for the +vertex labeled "A". If C<$caseSensitive> is 0, then vertices will not be case +sensitive. So "a" will be accepted as correct for the vertex labeled "A". + +If C<$context> is provided, then the strings will be added to that context. +Otherwise the strings will be added to the current context. + +=head2 numVertices + + $n = $graph->numVertices; + +This method returns the number of vertices in the graph. + +=head2 lastVertex + + $n = $graph->lastVertexIndex; + +This method returns the index of the last vertex in the graph (i.e., it is one +less then the number of vertices). + +=head2 numEdges + + $n = $graph->numEdges; + +This method returns the number of edges in the graph. + +=head2 labels + + $labels = $graph->labels; + $graph->labels($labels); + +Get or set the vertex labels for the graph. In both examples above, C<$labels> +is a reference to an array of strings, for example, C<[ 'M', 'N', 'O', 'P' ]>. + +=head2 labelsString + + $labelsString = $graph->labelsString; + +This returns the vertex labels joined with a comma. This is a convenience method +for displaying labels in a problem. For example, the labels string can be +inserted into a PGML block with C<< [`[$graph->labelsString]`] >>. + +=head2 vertexLabel + + $label = $graph->vertexLabel($index); + +This method returns the label of the vertex at index C<$index> in the graph. + +=head2 vertexIndex + + $index = $graph->vertexIndex($label); + +This method returns the index of the vertex in the graph that is labeled +C<$label> if the given C<$label> exists for a vertex, and -1 otherwise. + +=head2 vertexDegree + + $degree = $graph->vertexDegree($index); + +This method returns the degree of the vertex at index C<$index> in the graph. + +=head2 degrees + + @degrees = $graph->degrees; + +This method returns an array of the degrees of the vertices in the graph. + +=head2 numComponents + + $c = $graph->numComponents; + +This method returns the number of connected components in the graph. + +=head2 components + + @c = $graph->components; + +This method returns an array containing references to arrays that form a +partition of the vertex indices into the connected components of the graph. + +=head2 edgeWeight + + $c = $graph->edgeWeight($i, $j); + $graph->edgeWeight($i, $j, $weight); + +Get or set the weight of the edge between the vertices at indices C<$i> and +C<$i>. If the optional third C<$weight> argument is provided, then the weight +of the edge between the vertices at indices C<$i> and C<$j> will be set to +C<$weight>. In any case, the weight of the edge between the vertices at indices +C<$i> and C<$j> will be returned. Note that an edge weight of zero means that +there is no edge connecting the vertices. + +Setting the edge weight to zero is equivalent to removing the edge if it was +nonzero, and setting the edge weight to a nonzero number when it was zero adds +an edge. However, if your only intent is to add or remove an edge, then it is +recommended to use the C or C methods instead for clarity +in the code. + +=head2 addEdge + + $graph->addEdge($i, $j, $weight); + +Add and edge that connects the vertices at indices C<$i> and C<$j>. The +C<$weight> argument is optional, and if not provided the weight of the edge will +be set to 1. + +=head2 removeEdge + + $graph->removeEdge($i, $j); + +Remove the edge that connects the vertices at indices C<$i> and C<$j>. This is +accomplished by setting the weight of the edge between the two vertices to 0. + +=head2 hasEdge + + $graph->hasEdge($i, $j); + +This method returns a true value if the graph has an edge that connects the +vertices at indices C<$i> and C<$j>, and a false value otherwise. + +=head2 setRandomWeights + + $graph->setRandomWeights(%options); + +Set random weights for the edges of the graph. This method does not add or +remove any edges, but randomly sets the weights of the existing edges. + +The arguments that may be passed in C<%options> are as follows. If neither of +the C or C options is given, then this +method does not change the current edge weights of the graph. + +=over + +=item startEdgeWeight + +If this option is given, then it must be an integer that is greater than zero. +In this case this will be the first edge weight used in the generated graph, and +the other edge weights will be determined by adding increments of the +following C option. + +=item edgeWeightIncrement + +If this option is given, then it must be an integer that is greater than zero. +Note that this is only used if C is also given and is greater +than zero, and in that case increments of this number will be added to the +C and used for the weights of the edges. If the +C is given, is an integer greater than 0, and this option is +undefined, then an edge weight increment of 1 will be used. + +=item edgeWeightRange + +If this is given, then it must be a reference to an array of two or three +numbers. In this case the weights of the edges will be a random number from the +first number to the second number with increments of the optional third number +(the increment defaults to 1). In fact the elements of this array are passed +directly to the C function (see L). Note that the C option takes precedence over this +option, and if it is given and is an integer greater than one, then it will be +used instead of this option. + +=item edgeCount + +The number of edges in the graph for which to set random weights. If this +option is not given, then the number of edges in the graph will be computed, and +all the weights of all existing edges will be randomized. If the number of edges +in the graph is already known, then set this option to that value for efficiency +so that this method does not need to compute that number again. + +=back + +=head2 isEqual + + $graph->isEqual($other); + +This method returns 1 if the graph object represented by C<$graph> and the graph +represented by C<$other> are the same. This does not necessarily return 1 for +isomorphic graphs. The two graphs must literally have the same adjacency matrix. + +=head2 isIsomorphic + + $graph->isIsomorphic($other); + +This method returns 1 if the graph represented by C<$graph> and the graph +represented by C<$other> are isomorphic. + +WARNING: This method uses a brute force approach and compares the first graph to +all possible permutations of the other graph, and so should not be used for +graphs with a large number of vertices (probably no more than 8). + +=head2 description + + $graph->description(%options); + +Returns a textual description of the graph. The string that is returned is +translated and is suitable to be used as the C text for the graph image +returned by one of the following image methods. Note that the description just +lists the vertices and edges, and does not describe the layout for the +specialized layout image methods. + +At this point the only option that can be set via the C<%options> argument is +C. If C is set to 1, then the edge weights will +be included in the description. If this is 0, then edge weights will not be +included. Default is 0. + +=head2 image + + $graph->image(%options); + +Constructs and returns a L object via the L macro that +provides a pictorial representation of the graph. The returned object may be +inserted into the problem via the C method (see L), L, or using the PGML image syntax +(for example, C<< [!alt text!]{$graph->image} >>). + +Note that the C, C, and C options that +can be set when the C object is created affect how the +graph is displayed. See the L function for details on those +options. Note that those options can also be set as properties of the object +anytime after construction and before this method is called (for example, +C<< $self->{bipartiteLayout = 1; >>). If none of those layout options are used, +then the vertices of the graph will be evenly spaced around the perimeter of a +circle with the vertex at index 0 on the right. + +The following options can be set via the C<%options> argument. + +=over + +=item showLabels + +If this is 1, then vertex labels will be shown. If this is 0, then vertex labels +will not be shown. Default is 1 (so labels must be explicitly hidden by setting +this to 0). + +=item showWeights + +If this is 1, then edge weights will be shown. If this is 0, then edge weights +will not be shown. Default is 0. + +Note that the display of edge weights often does not work well as it can be +unclear which edge a given weight belongs to in the image. Particularly if a +graph has a large number of edges. Graphs that are created via the +C function using the row and column C<$size> argument (and +hence are displayed using the grid layout) do work quite well for this. + +=item width + +This is the width of the image. Default is 250 (except in the case that the +C is used, in which case the default is the product of 140 and one +less than the number of columns in the grid layout). + +Note that the width can also be set via the L C +method, or via the width option for the PGML image syntax. + +=item height + +This is the height of the image. Default is undefined. + +Note that the height can also be set via the L C +method, or via the height option for the PGML image syntax. + +=item aspect_ratio + +If this is set and the C option above is not set, then the height of the +image will be computed from the width using this aspect_ratio. Default is 1. + +=back + +Note that aside from the C and C options, all other +options (including any not listed above) that are provided are actually just +passed to the underlying C object. + +=head2 gridLayoutImage + + $graph->gridLayoutImage(%options); + +This method is not intended to be used externally. It is called by the L +method if the C property is set for the graph object. If this method +is called directly and the C property is set, then it will still +work. Otherwise an exception will be thrown. It accepts the same options as the +L method. Note that the default value for the C option is the +product of 140 and one less than the number of columns in the grid layout. + +=head2 bipartiteLayoutImage + + $graph->bipartiteLayoutImage(%options); + +This method is not intended to be used externally. It is called by the L +method if the C property is set for the graph object. If this +method is called directly and the C property is set, then it +will still work. Otherwise an exception will be thrown. Note that an exception +will also be thrown if the C property is set to 1, and the +graph is not bipartite. It accepts the same options as the L method. + +=head2 wheelLayoutImage + + $graph->wheelLayoutImage(%options); + +This method is not intended to be used externally. It is called by the L +method if the C property is set for the graph object. If this +method is called directly and the C property is set, then it will +still work. Otherwise an exception will be thrown. It accepts the same options +as the L method. + +=head2 copy + + $copy = $graph->copy; + +This method returns a copy of C<$graph>. This is an exact copy with all +properties duplicated to the returned object. + +=head2 shuffle + + $shuffledGraph = $graph->shuffle($permuteLabels); + +This method returns a randomization of C<$graph> obtained by permuting the +vertices. The edges that connected the vertices in the original graph will still +connect the permuted vertices in the resulting graph. In other words the +returned graph is isomorphic to the original. + +If the optional C<$permuteLabels> argument is provided and is true, then the +labels will also be permuted with the vertices. Otherwise the labels will remain +in the same order that they were in the original graph, but the shuffled graph +will still have the same labels. + +If the C property is set to 1 for the original graph, then the +shuffled graph will also have also have that property set to 1. However, the +array form of the C property will not be preserved. + +The C property is not preserved in the shuffled graph in any case. + +The C property is preserved in the shuffled graph, and its value +will be permuted so that the vertex it marked as the hub of the wheel is still +the hub. This means that the hub of the wheel will still be displayed in the +center when the L method is used to display the graph. If that is not +desired, then delete the C property for the result. For example, + + $shuffledGraph = $graph->shuffle; + delete $shuffledGraph->{wheelLayout}; + +=head2 nearestNeighborPath + + ($path, $weight) = $graph->nearestNeighborPath($vertex); + +This is an implementation of the nearest neighbor algorithm. It attempts to find +the shortest path starting at the vertex indexed by C<$vertex> that visits all +vertices in the graph exactly once and returns to the starting vertex. Note that +if such a path is not possible, then the algorithm will go as far as it can, but +the path that is returned may not visit all vertices and may not be a circuit. +This method will always succeed for complete graphs. + +The method returns a list whose first entry is a reference to an array +containing the path found, and whose second entry is the total weight of that +path. + +=head2 kruskalGraph + + ($tree, $treeWeight, $treeWeights, $algorithmSteps) = $graph->kruskalGraph($vertex); + +This is an implementation of Kruskal's algorithm. It attempts to find a minimum +spanning tree or forest for the graph. Note that if the graph is connected, then +the result will be a tree, and otherwise it will be a forest consisting of +minimal spanning trees for each component. + +The method returns a list with four entries. The first entry is a +C object representing the tree or forest found. The +second entry is the total weight of that tree or forest. The third entry is a +reference to an array containing the weights of the edges in the tree or forest +in the order that they are added by the algorithm. The fourth entry is a +reference to an array of array references that represent the steps of Kruskal's +algorithm. + +The returned representation of the steps in the algorithm will be a reference to +an array of array references where each array reference is of the form C<[$i, +$j, $weight, $accepted]>. This is where C<$i> and C<$j> are the indices of the +vertices connected by an edge in the graph, and C<$weight> is the weight of that +edge. These arrays will be sorted in ascending order of weight, i.e., the order +the edge is considered by Kruskal's algorithm. The last C<$accepted> will be +either 0 or 1. It will be 0 if the edge is rejected by the algorithm, and 1 if +it is accepted by the algorithm. Note that this list may not contain all edges +of the original graph if there are edges that are never considered by the +algorithm because the minimal spanning tree is completed before those edges are +reached. + +=head2 hasEulerCircuit + + $hasEulerCircuit = $graph->hasEulerCircuit; + ($hasEulerCircuit, $message) = $graph->hasEulerCircuit; + +In scalar context this method returns 1 if the graph has an Euler circuit, and 0 +otherwise. In list context this method returns 1 if the graph has an Euler +circuit, and if the graph does not have an Euler circuit it returns a list whose +first entry is 0 and whose second entry is a message stating the reason that the +graph does not have an Euler circuit. + +=head2 hasEulerTrail + + $hasEulerTrail = $graph->hasEulerTrail; + ($hasEulerTrail, $message) = $graph->hasEulerTrail; + +In scalar context this method returns 1 if the graph has an Euler trail, and 0 +otherwise. In list context this method returns 1 if the graph has an Euler +trail, and if the graph does not have an Euler trail it returns a list whose +first entry is 0 and whose second entry is a message stating the reason that the +graph does not have an Euler trail. + +=head2 pathIsEulerTrail + + $pathIsEulerTrail = $graph->pathIsEulerTrail(@path); + ($pathIsEulerTrail, $message) = $graph->pathIsEulerTrail(@path); + +The C<@path> argument should contain a list of vertex indices in the graph +representing a path in the graph. In scalar context this method returns 1 if the +path forms an Euler trail in the graph, and 0 otherwise. In list context this +method returns 1 if the path forms an Euler trail in the graph, and if the path +does not form an Euler trail in the graph it returns a list whose first entry is +0 and whose second entry is a message stating the reason that the path does not +form an Euler trail. + +=head2 pathIsEulerCircuit + + $pathIsEulerCircuit = $graph->pathIsEulerCircuit(@path); + ($pathIsEulerCircuit, $message) = $graph->pathIsEulerCircuit(@path); + +The C<@path> argument should contain a list of vertex indices in the graph +representing a path in the graph. In scalar context this method returns 1 if the +path forms an Euler circuit in the graph, and 0 otherwise. In list context this +method returns 1 if the path forms an Euler circuit in the graph, and if the +path does not form an Euler circuit in the graph it returns a list whose first +entry is 0 and whose second entry is a message stating the reason that the path +does not form an Euler circuit. + +=head2 hasCircuit + + $hasCircuit = $graph->hasCircuit; + +This method returns 1 if the graph has a circuit, and 0 otherwise. + +=head2 isTree + + $isTree = $graph->isTree; + ($isTree, $message) = $graph->isTree; + +In scalar context this method returns 1 if the graph is a tree, and 0 otherwise. +In list context this method returns 1 if the graph is a tree, and if the graph +is not a tree it returns a list whose first entry is 0 and whose second entry is +a message stating the reason that the graph is not a tree. + +=head2 isForest + + $isForest = $graph->isForest; + ($isForest, $message) = $graph->isForest; + +In scalar context this method returns 1 if the graph is a forest, and 0 +otherwise. In list context this method returns 1 if the graph is a forest, and +if the graph is not a forest it returns a list whose first entry is 0 and whose +second entry is a message stating the reason that the graph is not a forest. + +=head2 isBipartite + + $isBipartite = $graph->isBipartite; + +This method returns 1 if the graph is bipartite, and 0 otherwise. + +=head2 bipartitePartition + + ($upper, $lower) = $graph->bipartitePartition; + +If the graph is bipartite, then this method returns a list containing two +entries that form a partition of the vertices of the graph into two sets for +which no two vertices in the same set have an edge connecting them. If the graph +is not bipartite, then this method returns undefined. + +=head2 dijkstraPath + + ($distance, @path) = $graph->dijkstraPath($start, $end); + +This is an implementation of Dijkstra's algorithm for finding the shortest path +between nodes in a weighted graph. + +The C<$start> and C<$end> arguments are required and should be indices of two +vertices in the graph representing the start vertex and end vertex for which to +find the shortest path between. + +The return value will be a list whose first entry is the shortest distance from +the start vertex to the end vertex, and whose remaining entries are the indices +of the vertices that form the shortest path from the start vertex to the end +vertex. + +=head2 sortedEdgesPath + + ($pathWeights, $algorithmSteps, $sortedGraph) = $graph->sortedEdgesPath; + +This is an implementation of the sorted edges algorithm for finding a low cost +Hamiltonian circuit in a graph. That is a path that visits each vertex in the +graph exactly once. The return value will be a list with three entries. The +first entry is a reference to an array containing the weights of the edges in +the path in the order that they are chosen by the algorithm, the second entry is +a reference to an array of array references that represent the steps of the +sorted edges algorithm, and the third entry is the resulting sorted edges graph. + +The returned representation of the steps in the algorithm will be a reference to +an array of array references where each array reference is of the form +C<[$i, $j, $weight, $reason]>. This is where C<$i> and C<$j> are the indices of +the vertices connected by an edge in the graph, and C<$weight> is the weight of +that edge. These arrays will be sorted in ascending order of weight, i.e., the +order the edge is considered by the sorted edges algorithm. The C<$reason> will +be one of 0, 1, or 2. It will be 0 if the edge is chosen by the sorted edges +algorithm, 1 if the edge is rejected by the sorted edges algorithm because +adding it would have made a vertex in the path have more than two edges +connected to it, and 2 if adding the edge would have created a circuit in the +path (before the path is completed). Note that this list may not contain all +edges of the original graph if there are edges that are never considered by the +algorithm because the circuit is completed before those edges are reached. + +The returned sorted edges graph will contain a Hamiltonian circuit from the +original graph if one exists. In any case the graph will contain all edges +chosen in the algorithm. + +=head2 chromaticNumber + + $graph->chromaticNumber; + +This method returns the chromatic number of the graph. That is the minimum +number of colors required to color the vertices such that no two adjacent +vertices share the same color. + +=head2 kColoring + + $graph->kColoring($k); + +The argument C<$k> is required and is the desired number of colors for which to +find a k-coloring. That is a coloring of the graph consisting of at most k +colors such that no two adjacent vertices share the same color. If such a +coloring is possible, then this method will return a list with the number of +entries equal to the number of vertices in the graph, and whose i-th entry will +be an integer from 1 to k where the integers represent a coloring for the vertex +with index i in the graph and such that the list forms a k-coloring. If a +k-coloring is not possible, then this method returns undefined. + +=head1 EdgeSet Context + +A context for edge sets is provided. To activate the context Call + + Context('EdgeSet'); + +Then C and +C objects can be constructed with + + $edge = Compute('{B, E}'); + $edgeSet = Compute('{{A, B}, {A, E}, {A, F}, {E, F}}'); + +or by using the C or C methods described before (those methods +can also be used outside of this context). Note that the vertices must be added +as strings to the context and marked as being vertices with the C +flag before constructing the C or C. For example, by calling + + Context()->strings->add( + map { $_ => { isVertex => 1, caseSensitive => 1 } } 'A' .. 'F' + ); + +If it is preferred that the vertices not be case sensitive, then remove +C<< caseSensitive => 1 >> from the above call. + +If the vertices in the C or C belong to a C +object that has already been constructed, then the L +method can instead be used to add the vertices to the context. + +=cut diff --git a/macros/math/SimpleGraphCatalog.pl b/macros/math/SimpleGraphCatalog.pl new file mode 100644 index 0000000000..c90fd2c711 --- /dev/null +++ b/macros/math/SimpleGraphCatalog.pl @@ -0,0 +1,10735 @@ + +=head1 NAME + +SimpleGraphCatalog.pl - A catalog of all simple graphs with fewer than 8 +vertices. + +=head1 DESCRIPTION + +This macro defines the C<$graphCatalog> hash. It has 8 keys (0, 1, ..., 8). the +value of each key is a reference to an array containing all possible graphs (up +to isomorphism) with that number of vertices. + +Graphs are represented as a reference to an array of arrays which are suitable +for passing to L to create a +C object. + +For example, C<$graphCatalog{3}[3]> is + + [ [ 0, 1, 1 ], [ 1, 0, 1 ], [ 1, 1, 0 ] ] + +=cut + +BEGIN { strict->import; } + +sub _GraphCatalog_init { } + +%main::graphCatalog = ( + 1 => [ [ [0] ] ], + 2 => [ [ [ 0, 0 ], [ 0, 0 ] ], [ [ 0, 1 ], [ 1, 0 ] ] ], + 3 => [ + [ [ 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 ] ], + ], + 4 => [ + [ [ 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 ] ] + ], + 5 => [ + [ [ 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 ] ] + ], + 6 => [ + [ + [ 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 ] + ] + ], + 7 => [ + [ + [ 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 ] + ] + ] +); + +1; diff --git a/macros/math/algebraMacros.pl b/macros/math/algebraMacros.pl index bbe70005de..da5772496b 100644 --- a/macros/math/algebraMacros.pl +++ b/macros/math/algebraMacros.pl @@ -38,7 +38,7 @@ =head2 xgcd =cut -# by Dick Lane http://webwork.maa.org/moodle/mod/forum/discuss.php?d=2286 +# by Dick Lane https://forums.openwebwork.org/mod/forum/discuss.php?d=2286 sub xgcd ($$) { my ($a, $b, $x, $y, $s, $t) = (@_, 1, 0, 0, 1); diff --git a/macros/math/bizarroArithmetic.pl b/macros/math/bizarroArithmetic.pl index 1349c22612..d7aa641b8b 100644 --- a/macros/math/bizarroArithmetic.pl +++ b/macros/math/bizarroArithmetic.pl @@ -84,7 +84,7 @@ =head1 DESCRIPTION package bizarro; -#This f just stretches complex numbers by a positve real that +#This f just stretches complex numbers by a positive real that #depends in a nontrivial away on the magnitude of z sub f { my $z = shift; diff --git a/macros/math/draggableProof.pl b/macros/math/draggableProof.pl index 9a79a7fb33..c705b5524f 100644 --- a/macros/math/draggableProof.pl +++ b/macros/math/draggableProof.pl @@ -3,7 +3,7 @@ =head1 NAME -draggableProof.pl - Allows the contructions of problems with draggable statements for proofs. +draggableProof.pl - Allows the constructions of problems with draggable statements for proofs. =head1 DESCRIPTION @@ -66,19 +66,26 @@ =head1 DESCRIPTION DamerauLevenshtein => 0 or 1 InferenceMatrix => IrrelevancePenalty => - ResetButtonText => + cmpOptions => -Their usage is explained in the example below. + resetButtonText => + reorderText => + moveText => + helpButtonText => + closeHelpButtonText => + dragAndDropHelpText => + +The last six options above are really options of a C object and are +passed to that module on construction. See the L +for details on those options. Note that C is a deprecated alias +for the C option. + +The usage of all but the last six options are demonstrated in the example below. =head1 SYNOPSIS DOCUMENT(); - loadMacros( - 'PGstandard.pl', - 'PGML.pl', - 'MathObjects.pl', - 'draggableProof.pl' - ); + loadMacros('PGstandard.pl', 'PGML.pl', 'draggableProof.pl', 'PGcourse.pl'); $draggable = DraggableProof( # The proof given in the correct order. @@ -138,11 +145,6 @@ =head1 SYNOPSIS # The default value if not given is 1. IrrelevancePenalty => 1 - # This is the text label for the button shown that resets the drag and - # drop element to its default state. The default value if not given is - # "Reset". - ResetButtonText => 'zurücksetzen' - # These are options that will be passed to the $draggable->cmp method. cmpOptions => { checker => sub { ... } } ); @@ -160,8 +162,8 @@ =head1 CUSTOM CHECKERS Custom checkers can also be used by passing the C or C options to the C method. See -L, and -L for details on +L, and +L for details on how to use these. Note that if using a standard C the correct and student answers @@ -203,7 +205,6 @@ sub new { NumBuckets => 2, lines => [ @$statements, @$extra_statements ], numNeeded => scalar(@$statements), - ResetButtonText => 'Reset', cmpOptions => {}, Levenshtein => 0, DamerauLevenshtein => 0, @@ -212,6 +213,9 @@ sub new { %options }; + # Backwards compatibility. + $base->{resetButtonText} = delete $base->{ResetButtonText} if defined $base->{ResetButtonText}; + $base->{order} = do { my @indices = 0 .. $#{ $base->{lines} }; [ map { splice(@indices, main::random(0, $#indices), 1) } @indices ]; @@ -270,6 +274,16 @@ sub ANS_NAME { sub ans_rule { my $self = shift; + my %options; + + for my $option ( + 'resetButtonText', 'reorderText', 'moveText', 'helpButtonText', + 'closeHelpButtonText', 'dragAndDropHelpText' + ) + { + $options{$option} = $self->{$option} if defined $self->{$option}; + } + if ($self->{NumBuckets} == 2) { $self->{dnd} = DragNDrop->new( $self->ANS_NAME, @@ -278,14 +292,13 @@ sub ans_rule { { indices => [ 0 .. $#{ $self->{lines} } ], label => $self->{SourceLabel} }, { indices => [], label => $self->{TargetLabel} } ], - resetButtonText => $self->{ResetButtonText} + %options ); } elsif ($self->{NumBuckets} == 1) { $self->{dnd} = DragNDrop->new( $self->ANS_NAME, $self->{shuffledLines}, - [ { indices => [ 0 .. $#{ $self->{lines} } ], label => $self->{TargetLabel} } ], - resetButtonText => $self->{ResetButtonText} + [ { indices => [ 0 .. $#{ $self->{lines} } ], label => $self->{TargetLabel} } ], %options ); } diff --git a/macros/math/draggableSubsets.pl b/macros/math/draggableSubsets.pl index 53328cb414..131a097e5a 100644 --- a/macros/math/draggableSubsets.pl +++ b/macros/math/draggableSubsets.pl @@ -66,24 +66,38 @@ =head1 DESCRIPTION DefaultSubsets => OrderedSubsets => 0 or 1 - AllowNewBuckets => 0 or 1 - BucketLabelFormat => - ResetButtonText => - AddButtonText => - RemoveButtonText => - ShowUniversalSet => 0 or 1 - UniversalSetLabel => - -Their usage is demonstrated in the example below. + cmpOptions => + + allowNewBuckets => 0 or 1 + bucketLabelFormat => + resetButtonText => + addButtonText => + removeButtonText => + showUniversalSet => 0 or 1 + universalSetLabel => + addFromUniversalText => + removeUniversalItemText => , + reorderText => + moveText => + helpButtonText => + closeHelpButtonText => + dragAndDropHelpText => + +All of the options above except for the first three are really options of a +C object and are passed to that module on construction. See the +L for details on those options. Note that +C, C, C, C, +C, C, and C are +deprecated aliases for the corresponding option with the first letter lower +case. Note that the default value of C is 1 for this macro (it +is 0 for the C package). + +The usage of the first three options is demonstrated in the example below. =head1 SYNOPSIS DOCUMENT(); - loadMacros( - 'PGstandard.pl', - 'PGML.pl', - 'draggableSubsets.pl' - ); + loadMacros('PGstandard.pl', 'PGML.pl', 'draggableSubsets.pl', 'PGcourse.pl'); $draggable = DraggableSubsets( # Full set. Make sure to use "\(...\)" for math and not "`...`" for correct display. @@ -129,42 +143,6 @@ =head1 SYNOPSIS # The default value if not given is 0. OrderedSubsets => 0, - # 0 means no new buckets may be added by student. 1 means otherwise. - # The default value if not given is 1. - AllowNewBuckets => 1, - - # If this option is defined then labels for buckets for which a specific - # label is not provided will be created by replacing %s with the bucket - # number to this prefix. These labels will also be used for buckets - # added by the user if AllowNewBuckets is 1. This string should contain - # exactly one instance of %s. The default value if not given is - # undefined. - BucketLabelFormat => 'Subset %s' - - # This is the text label for the button shown that resets the drag and - # drop element to its default state. The default value if not given is - # "Reset". - ResetButtonText => 'zurücksetzen' - - # This is the text label for the button shown that adds new buckets. - # The button is only shown if AllowNewBuckets is 1. - # The default value if not given is "Add Bucket". - AddButtonText => 'Add Subset' - - # This is the text label for the remove button that is added to any - # removable buckets. The default value if not given is "Remove". - RemoveButtonText => 'Delete' - - # If this is true then a separate bucket containing the full set passed - # as the first argument above (the universal set) will be shown, and the - # elements of the set can be distributed to the other subsets (or - # buckets) that are shown. The default value if not given is 0. - ShowUniversalSet => 1, - - # Label for the bucket representing the universal set. - # The default value if not given is "Universal Set". - UniversalSetLabel => 'Universal Set', - # These are options that will be passed to the $draggable->cmp method. cmpOptions => { checker => sub { ... } } ); @@ -188,7 +166,7 @@ =head1 SYNOPSIS =head1 CUSTOM CHECKERS A custom checkers can also be used by passing the C option to the -C method. See L +C method. See L for details on how to use a custom list checker. This follows the usual rules for the return value of the C method. @@ -214,23 +192,26 @@ sub new { my ($invocant, $set, $subsets, %options) = @_; my $base = bless { - set => $set, - DefaultSubsets => [], - OrderedSubsets => 0, - AllowNewBuckets => 1, - cmpOptions => {}, - BucketLabelFormat => undef, - ResetButtonText => 'Reset', - AddButtonText => 'Add Bucket', - RemoveButtonText => 'Remove', - ShowUniversalSet => 0, - UniversalSetLabel => 'Universal Set', + set => $set, + DefaultSubsets => [], + OrderedSubsets => 0, + allowNewBuckets => 1, + cmpOptions => {}, %options }, ref($invocant) || $invocant; Value::Error('Answer subsets must be an array reference.') unless ref($subsets) eq 'ARRAY'; + # Backwards compatibility. + for my $option ( + 'AllowNewBuckets', 'BucketLabelFormat', 'ShowUniversalSet', 'ResetButtonText', + 'AddButtonText', 'RemoveButtonText', 'UniversalSetLabel' + ) + { + $base->{ lcfirst($option) } = delete $base->{$option} if defined $base->{$option}; + } + my %seenIndices; for my $subset (@$subsets) { Value::Error('Each answer subset must be a reference to an array of indices.') @@ -238,8 +219,8 @@ sub new { for (@$subset) { Value::Error('An index in an answer subset is out of range.') unless $_ < @$set; Value::Error('An index is repeated in multiple answer subsets. ' - . 'This can only be the case if ShowUniversalSet is 1.') - if !$base->{ShowUniversalSet} && $seenIndices{$_}; + . 'This can only be the case if showUniversalSet is 1.') + if !$base->{showUniversalSet} && $seenIndices{$_}; $seenIndices{$_} = 1; } } @@ -255,8 +236,8 @@ sub new { for (@{ $subset->{indices} }) { Value::Error('An index in a default subset is out of range.') unless $_ < @$set; Value::Error('An index is repeated in multiple default subsets.' - . 'This can only be the case if ShowUniversalSet is 1.') - if !$base->{ShowUniversalSet} && $seenIndices{$_}; + . 'This can only be the case if showUniversalSet is 1.') + if !$base->{showUniversalSet} && $seenIndices{$_}; $seenIndices{$_} = 1; } } @@ -327,16 +308,19 @@ sub ans_rule { push(@buckets, { label => '', indices => [ 0 .. $#{ $self->{set} } ] }); } - $self->{dnd} = DragNDrop->new( - $self->ANS_NAME, $self->{shuffledSet}, \@buckets, - allowNewBuckets => $self->{AllowNewBuckets}, - bucketLabelFormat => $self->{BucketLabelFormat}, - resetButtonText => $self->{ResetButtonText}, - addButtonText => $self->{AddButtonText}, - removeButtonText => $self->{RemoveButtonText}, - showUniversalSet => $self->{ShowUniversalSet}, - universalSetLabel => $self->{UniversalSetLabel}, - ); + my %options; + + for my $option ( + 'allowNewBuckets', 'bucketLabelFormat', 'resetButtonText', 'addButtonText', + 'removeButtonText', 'showUniversalSet', 'universalSetLabel', 'addFromUniversalText', + 'removeUniversalItemText', 'reorderText', 'moveText', 'helpButtonText', + 'closeHelpButtonText', 'dragAndDropHelpText' + ) + { + $options{$option} = $self->{$option} if defined $self->{$option}; + } + + $self->{dnd} = DragNDrop->new($self->ANS_NAME, $self->{shuffledSet}, \@buckets, %options); my $ans_rule = main::NAMED_HIDDEN_ANS_RULE($self->ANS_NAME); if ($main::displayMode eq 'TeX') { diff --git a/macros/math/tableau.pl b/macros/math/tableau.pl index 91d33a0855..4f4c67f474 100644 --- a/macros/math/tableau.pl +++ b/macros/math/tableau.pl @@ -75,7 +75,7 @@ =head2 tableauEquivalence tableauEquivalence compares two matrices up to reshuffling the rows and multiplying each row by a constant. -It is equivalent up to multiplying on the left by a permuation matrix +It is equivalent up to multiplying on the left by a permutation matrix or a (non-uniformly constant) diagonal matrix. It is appropriate for comparing augmented matrices representing a system of equations since the order of the equations is unimportant. This applies to tableaus for @@ -157,9 +157,9 @@ =head2 new =head1 REFERENCES -MathObject Matrix methods: L -MathObject Contexts: L -CPAN RealMatrix docs: L +MathObject Matrix methods: L +MathObject Contexts: L +CPAN RealMatrix docs: L More references: L @@ -589,7 +589,7 @@ sub objective_row { Parameters: () or (list) Returns: A MathObjectMatrix - Useage: + Usage: $MathObjectmatrix = $self->current_tableau $MathObjectmatrix = $self->current_tableau(3,4) #updates basis to (3,4) @@ -1225,7 +1225,7 @@ sub dual_lop { @array = $self->primal2dual(2,3,4) -Maps LOP column indices to dual LOP indicies (basis of complementary slack property) +Maps LOP column indices to dual LOP indices (basis of complementary slack property) =cut @@ -1234,7 +1234,7 @@ sub dual_lop { @array = $self->dual2primal(2,3,4) -Maps dual LOP column indices to primal LOP indicies (basis of complementary slack property). +Maps dual LOP column indices to primal LOP indices (basis of complementary slack property). Inverse of primal2dual method. @@ -1477,7 +1477,7 @@ sub submatrix { =cut # This was written by Davide Cervone. -# http://webwork.maa.org/moodle/mod/forum/discuss.php?d=2970 +# https://forums.openwebwork.org/mod/forum/discuss.php?d=2970 # taken from MatrixReduce.pl from Paul Pearson sub change_matrix_entry { diff --git a/macros/misc/PCCmacros.pl b/macros/misc/PCCmacros.pl index 7a4c82aa17..58c5038304 100644 --- a/macros/misc/PCCmacros.pl +++ b/macros/misc/PCCmacros.pl @@ -755,7 +755,7 @@ sub radicalListCheck { # # Check that the student's answer is an assignment (or whatever the right type) # - my $assingmentMessageGiven = 0; + my $assignmentMessageGiven = 0; my $nosolutionMessageGiven = 0; if (defined($p)) { if ($p eq "no real solutions") { @@ -763,7 +763,7 @@ sub radicalListCheck { $nosolutionMessageGiven = 1; } elsif ($p->type ne "Assignment") { push(@errors, "Your $ith entry should be written $var=_____"); - $assingmentMessageGiven = 1; + $assignmentMessageGiven = 1; } } @@ -819,9 +819,9 @@ sub radicalListCheck { } } push(@errors, "Your $ith solution is not correct") - if (!$numericallyCorrect and ($n > 1) and !$assingmentMessageGiven); + if (!$numericallyCorrect and ($n > 1) and !$assignmentMessageGiven); push(@errors, "Your solution is not correct") - if (!$numericallyCorrect and ($n == 1) and !$assingmentMessageGiven and !$nosolutionMessageGiven); + if (!$numericallyCorrect and ($n == 1) and !$assignmentMessageGiven and !$nosolutionMessageGiven); } } diff --git a/macros/misc/randomPerson.pl b/macros/misc/randomPerson.pl index d3bf6b66a8..67bb541dd7 100644 --- a/macros/misc/randomPerson.pl +++ b/macros/misc/randomPerson.pl @@ -49,10 +49,10 @@ =head2 Usage Depending on the he/she/they pronoun, the methods C, C, C and C (with or without capitalization) will select the correct subject, object, -possession, possesive forms of the pronoun. Also, note that C is one of a +possession, possessive forms of the pronoun. Also, note that C is one of a few special verbs with irregular conjugation. -If you would like multiple people to be randomly choosen with unique names, use +If you would like multiple people to be randomly chosen with unique names, use @persons = randomPerson(n => 4); diff --git a/macros/parsers/parserAssignment.pl b/macros/parsers/parserAssignment.pl index 5faea6722c..b537122fcf 100644 --- a/macros/parsers/parserAssignment.pl +++ b/macros/parsers/parserAssignment.pl @@ -379,7 +379,7 @@ sub cmp_class { sub showClass { cmp_class(@_) } # -# Convert varaible names to those used in the correct answer, if the +# Convert variable names to those used in the correct answer, if the # student answer uses different ones # sub compare { diff --git a/macros/parsers/parserCheckboxList.pl b/macros/parsers/parserCheckboxList.pl index 593192d271..9a6c86c4ea 100644 --- a/macros/parsers/parserCheckboxList.pl +++ b/macros/parsers/parserCheckboxList.pl @@ -137,7 +137,8 @@ =head1 DESCRIPTION =item C string >>> -Specifies the text to put between the checkboxes. Default: $BR +Specifies the text to put between the checkboxes. Note that this is only used in +HTML display modes, and is ignored for PTX and TeX display modes. Default: $BR =item C choice >>> @@ -157,6 +158,7 @@ =head1 DESCRIPTION the literal correct answer, not an index to it. Default: 0 =item C 0 or 1 >>> + In static output, such as PDF or PTX, this controls whether or not the list of answer options is displayed. (The text preceding the list of answer options might make printing the answer option list @@ -253,7 +255,7 @@ sub new { $self->getCheckedChoices($self->{checked}); $context->strings->add(map { ($self->{values}[$_] => {}) } (0 .. ($self->{n} - 1))); - $_ = Value::makeValue($_, context => $context) for @{ $self->data }; + $_ = $context->Package('String')->make($context, $_) for @{ $self->data }; return $self; } @@ -614,7 +616,10 @@ sub CHECKS { $checks[-1] .= "
    "; } - return wantarray ? @checks : join($main::displayMode eq 'PTX' ? '' : $self->{separator}, @checks); + return + wantarray + ? @checks + : join($main::displayMode eq 'PTX' || $main::displayMode eq 'TeX' ? '' : $self->{separator}, @checks); } sub protect { diff --git a/macros/parsers/parserCustomization.pl b/macros/parsers/parserCustomization.pl index 29aa9e79e0..98810be02e 100644 --- a/macros/parsers/parserCustomization.pl +++ b/macros/parsers/parserCustomization.pl @@ -16,7 +16,7 @@ =head1 DESCRIPTION $context{Vector}->parens->remove('<'); To allow vectors to be entered with parens (and displayed with -parens) rather than angle-brakets, use +parens) rather than angle-brackets, use $context{Vector} = Parser::Context->getCopy("Vector"); $context{Vector}->{cmpDefaults}{Vector} = {promotePoints => 1}; diff --git a/macros/parsers/parserFormulaWithUnits.pl b/macros/parsers/parserFormulaWithUnits.pl index a5029f4431..b5d0f3a532 100644 --- a/macros/parsers/parserFormulaWithUnits.pl +++ b/macros/parsers/parserFormulaWithUnits.pl @@ -39,14 +39,14 @@ =head1 SYNOPSIS Finally, the newUnit option can also be an array ref containing any number of new units to add. A common reason for doing this would be to add the plural -version of the unit as an equilvalent unit. E.G. +version of the unit as an equivalent unit. E.G. $newUnits = ['apple',{name=>'apples',conversion=>{factor=>1,apple=>1}}]; $a = FormulaWithUnits("3x apples",{newUnit=>$newUnits}); In this case both 3x apple and 3x apples would be accepted as answers. -Note: English pluralization is suprisingly hard, so WeBWorK will make no +Note: English pluralization is surprisingly hard, so WeBWorK will make no attempt to display a grammerically correct result. =head1 SEE ALSO diff --git a/macros/parsers/parserImplicitEquation.pl b/macros/parsers/parserImplicitEquation.pl index bba7106564..e068cb4fab 100644 --- a/macros/parsers/parserImplicitEquation.pl +++ b/macros/parsers/parserImplicitEquation.pl @@ -190,7 +190,7 @@ sub Init { BisectionCutoff => 40, ); - main::Context("ImplicitEquation"); ### FIXEME: probably should require author to set this explicitly + main::Context("ImplicitEquation"); ### FIXME: probably should require author to set this explicitly main::PG_restricted_eval('sub ImplicitEquation {ImplicitEquation->new(@_)}'); } diff --git a/macros/parsers/parserMultiAnswer.pl b/macros/parsers/parserMultiAnswer.pl index 2bc718521c..ab8c1ed3a0 100644 --- a/macros/parsers/parserMultiAnswer.pl +++ b/macros/parsers/parserMultiAnswer.pl @@ -14,7 +14,6 @@ package parser::MultiAnswer; our $separator = ';'; # separator for singleResult previews my @ans_defaults = ( - checker => sub {0}, showCoordinateHints => 0, showEndpointHints => 0, showEndTypeHints => 0, @@ -90,17 +89,13 @@ sub cmp { die "Your checker must be a subroutine." if defined($self->{checker}); $self->{checker} = sub { my ($correct, $student, $self, $ans) = @_; - my @scores; - - for (0 .. $self->length - 1) { - push(@scores, $correct->[$_] == $student->[$_] ? 1 : 0); - } - return \@scores if $self->{partialCredit}; - for (@scores) { - return 0 unless $_; + return $self->{scores} if $self->{partialCredit}; + for (@{ $self->{scores} }) { + return 0 unless $_ == 1; } return 1; - } + }; + $self->{checkTypes} = 'compatible' if $self->{checkTypes} && $self->{checkTypes} ne 'exact'; } if ($self->{allowBlankAnswers}) { @@ -281,7 +276,6 @@ sub entry_check { my $ANS = $self->{cmp}[$i]->evaluate($ans->{student_ans}); $self->{ans}[$i] = $ANS; $ANS->{type} = $ans->{type}; - $ANS->score(0); foreach my $id (keys %{$ans}) { $ANS->{$id} = $ans->{$id} unless defined($ANS->{$id}); @@ -306,21 +300,48 @@ sub perform_check { my $rh_ans = shift; my $context = $self->context; $context->clearError; + + # + # Get correct and student answers, and scores. + # Initially mark all answer blanks as wrong. + # my @correct; my @student; - foreach my $ans (@{ $self->{ans} }) { - push(@correct, $ans->{correct_value}); - push(@student, $ans->{student_value}); - return if $ans->{ans_message} || !defined($ans->{student_value}); + $self->{scores} = []; + for my $ans (@{ $self->{ans} }) { + push(@correct, $ans->{correct_value}); + push(@student, $ans->{student_value}); + push(@{ $self->{scores} }, $ans->{score}); + $ans->score(0); + } + + # + # Check for error messages and correct answer types + # + my $checkTypes = $self->{checkTypes}; + $checkTypes = 'equal' if $checkTypes && $checkTypes ne 'compatible'; + for my $ans (@{ $self->{ans} }) { + return if ($checkTypes ne 'compatible' && $ans->{ans_message}) || !defined($ans->{student_value}); return - if $self->{checkTypes} + if $checkTypes eq 'equal' && $ans->{student_value}->type ne $ans->{correct_value}->type && !($self->{allowBlankAnswers} && $ans->{student_ans} !~ m/\S/); + return + if $checkTypes eq 'compatible' + && !$ans->{correct_value}->typeMatch($ans->{student_value}, $ans) + && !($self->{allowBlankAnswers} && $ans->{student_ans} !~ m/\S/); } + + # + # Set the isPreview flag. + # my $inputs = $main::inputs_ref; $rh_ans->{isPreview} = $inputs->{previewAnswers} - || ($inputs_{action} && $inputs->{action} =~ m/^Preview/); + || ($inputs->{action} && $inputs->{action} =~ m/^Preview/); + # + # Call the user's answer checker. + # Parser::Context->current(undef, $context); # change to multi-answer's context my $flags = Value::contextSet($context, $self->cmp_contextFlags($rh_ans)); # save old context flags $context->{answerHash} = $rh_ans; # attach the answerHash @@ -329,16 +350,20 @@ sub perform_check { $context->{answerHash} = undef; # remove answerHash if (!@result && $context->{error}{flag}) { $self->cmp_error($self->{ans}[0]); return 1 } + # + # Mark the answers as correct or incorrect. + # my $result = (scalar(@result) > 1 ? [@result] : $result[0] || 0); if (ref($result) eq 'ARRAY') { die "Checker subroutine returned the wrong number of results" if (scalar(@{$result}) != $self->length); - foreach my $i (0 .. $self->length - 1) { $self->{ans}[$i]->score($result->[$i]) } + for my $i (0 .. $self->length - 1) { $self->{ans}[$i]->score($result->[$i]) } } elsif (Value::matchNumber($result)) { - foreach my $ans (@{ $self->{ans} }) { $ans->score($result) } + for my $ans (@{ $self->{ans} }) { $ans->score($result) } } else { die "Checker subroutine should return a number or array of numbers ($result)"; } + return; } @@ -490,16 +515,19 @@ =head2 checker # this checker will give full credit for any answers sub always_right { - my ($correct,$student,$multi_ans,$ans_hash) = @_; # get the parameters - return [ (1) x scalar(@$correct) ]; # return an array of scores + my ($correct, $student, $multi_ans, $ans_hash) = @_; # get the parameters + return [ (1) x scalar(@$correct) ]; # return an array of scores } - $multianswer_obj = $multianswer_obj->with(checker=>~~&always_right); + $multianswer_obj = $multianswer_obj->with(checker => ~~&always_right); If a C is not provided, a default checker is used. The default checker checks if each answer is equal to its correct answer (using the overloaded C<==> operator). If C<< partialCredit => 1 >>, the checker returns an array of 0s and 1s listing which answers are correct giving partial credit. If C<< partialCredit => 0 >>, the checker only returns 1 if all answers are correct, otherwise returns 0. +Note that C<< $multi_ans->{scores} >> will be set to a reference to an array containing the scores +from evaluating the individual answers and can be used inside the C routine. + =head2 partialCredit This is used with the default checker to determine if the default checker should reward partial @@ -524,9 +552,14 @@ =head2 cmpOpts =head2 checkTypes -Specifies whether the types of the student and professor's answers must match exactly -(C<< checkTypes => 1 >>) or just pass the usual type-match error checking (in which case, you should -check the types before you use the data). Default: 1. +Specifies whether the types of the student and instructor's answers must match and to what extent. +if C<< checkTypes => 'compatible' >> then the student answers only need to be compatible with the +instructor answers in the sense that they parse into objects that can be compared to the instructor +answers. If C<< checkTypes => 1 >> then the types of the student answers must match the types of the +instructor answers exactly. Otherwise no type checking is done other than the usual type-match error +checking (in which case, you should check the types before you use the data). Default: 1. +Note that if the default checker is used, i.e., if C is not set, then C<< checkTypes => 1 >> +is the same as C<< checkTypes => 'compatible' >>. =head2 allowBlankAnswers diff --git a/macros/parsers/parserNumberWithUnits.pl b/macros/parsers/parserNumberWithUnits.pl index 354a2442b4..84ef79efaf 100644 --- a/macros/parsers/parserNumberWithUnits.pl +++ b/macros/parsers/parserNumberWithUnits.pl @@ -37,14 +37,14 @@ =head1 DESCRIPTION Finally, the newUnit option can also be an array ref containing any number of new units to add. A common reason for doing this would be to add the plural -version of the unit as an equilvalent unit. E.G. +version of the unit as an equivalent unit. E.G. $newUnits = ['apple',{name=>'apples',conversion=>{factor=>1,apple=>1}}]; $a = NumberWithUnits("3 apples",{newUnit=>$newUnits}); In this case both 3 apple and 3 apples would be considered correct. -Note: English pluralization is suprisingly hard, so WeBWorK will make no +Note: English pluralization is surprisingly hard, so WeBWorK will make no attempt to display a grammerically correct result. =head1 SEE ALSO diff --git a/macros/parsers/parserOneOf.pl b/macros/parsers/parserOneOf.pl index 35ae6bc822..0daf8bac35 100644 --- a/macros/parsers/parserOneOf.pl +++ b/macros/parsers/parserOneOf.pl @@ -1,7 +1,7 @@ =head1 NAME -parserOneOf.pl - Implements a MathObject that allows students to entery any +parserOneOf.pl - Implements a MathObject that allows students to enter any one of several possible right answers. =head1 DESCRIPTION diff --git a/macros/parsers/parserPopUp.pl b/macros/parsers/parserPopUp.pl index 3688f05b73..5c69869ab5 100644 --- a/macros/parsers/parserPopUp.pl +++ b/macros/parsers/parserPopUp.pl @@ -17,11 +17,11 @@ =head1 DESCRIPTION $truefalse = DropDownTF(correct, options); where "choices" are the items in the drop-down list, "correct" is the -the correct answer for the group (or its index, with 0 being the -first one), and options are chosen from among those listed below. If -the correct answer is a number, it is interpreted as an index, even -if the array of choices are also numbers. (See the C below -for more details.) +correct answer for the group (or its index, with 0 being the first +one), and options are chosen from among those listed below. If the +correct answer is a number, it is interpreted as an index, even if the +array of choices are also numbers. (See the C below for more +details.) Note that drop-down menus cannot contain mathematical notation, only plain text. This is because the browser's native menus are used, and diff --git a/macros/parsers/parserRadioButtons.pl b/macros/parsers/parserRadioButtons.pl index 88a1122f28..83551ad62e 100644 --- a/macros/parsers/parserRadioButtons.pl +++ b/macros/parsers/parserRadioButtons.pl @@ -140,7 +140,9 @@ =head1 DESCRIPTION =item C string >>> -Specifies the text to put between the radio buttons. Default: $BR +Specifies the text to put between the radio buttons. Note that this is only used +in HTML display modes, and is ignored for PTX and TeX display modes. +Default: $BR =item C choice >>> @@ -699,7 +701,9 @@ sub BUTTONS { . qq{data-feedback-btn-add-class="ms-3">$radio[0]}; $radio[-1] .= ""; } - (wantarray) ? @radio : join(($main::displayMode eq 'PTX') ? '' : $self->{separator}, @radio); + wantarray + ? @radio + : join($main::displayMode eq 'PTX' || $main::displayMode eq 'TeX' ? '' : $self->{separator}, @radio); } sub protect { diff --git a/macros/parsers/parserRadioMultiAnswer.pl b/macros/parsers/parserRadioMultiAnswer.pl index 3cee48a56f..f27716789c 100644 --- a/macros/parsers/parserRadioMultiAnswer.pl +++ b/macros/parsers/parserRadioMultiAnswer.pl @@ -16,7 +16,7 @@ =head1 DESCRIPTION $rma = RadioMultiAnswer([ ['The unique solution is \(x=\) %s and \(y=\) %s.', 5, 6], ['There are an infinite number of solutions parameterized by ' - . '\(x=\) %s and \(y=\) %s.', '23-3t', 't'] + . '\(x=\) %s and \(y=\) %s.', '23-3x', 'x'], ['There are no solutions.'] ], 0); @@ -151,8 +151,14 @@ =head1 OPTIONS =item checkTypes (Default: checkTypes => 1) -Whether the types of the student and correct answers must match exactly or just pass the usual -type-match error checking (in which case, you should check the types before you use the data). +Specifies whether the types of the student and instructor's answers must match and to what extent. +if C<< checkTypes => 'compatible' >> then the student answers only need to be compatible with the +instructor answers in the sense that they parse into objects that can be compared to the instructor +answers. If C<< checkTypes => 1 >> then the types of the student answers must match the types of the +instructor answers exactly. Otherwise no type checking is done other than the usual type-match error +checking (in which case, you should check the types before you use the data). Default: 1. +Note that if the default checker is used, i.e., if C is not set, then C<< checkTypes => 1 >> +is the same as C<< checkTypes => 'compatible' >>. =item allowBlankAnswers (Default: allowBlankAnswers => 0) @@ -233,6 +239,14 @@ =head1 OPTIONS =back +=item showInStatic (Default: showInStatic => 1) + +In static output, such as PDF or PTX, this controls whether or not the list of answer options is +displayed. (The text preceding the list of answer options might make printing the answer option +list unnecessary in a static output format.) + +=back + =cut BEGIN { strict->import } @@ -252,7 +266,6 @@ package parser::RadioMultiAnswer; our $answerPrefix = 'RaDiOMuLtIaNsWeR_'; # answer rule prefix my @ans_defaults = ( - checker => sub {0}, showCoordinateHints => 0, showEndpointHints => 0, showEndTypeHints => 0 @@ -288,6 +301,7 @@ sub new { size => undef, checked => undef, uncheckable => 0, + showInStatic => 1, @inputs ); @@ -341,16 +355,18 @@ sub cmp { } unless (ref($self->{checker}) eq 'CODE') { + die "Your checker must be a subroutine." if defined $self->{checker}; $self->{checker} = sub { my ($correct, $student, $self, $ans) = @_; return 0 if ($correct->[0] != $student->[0]); for (0 .. $#{ $correct->[ $correct->[0] ] }) { - return 0 if $correct->[ $correct->[0] ][$_] != $student->[ $correct->[0] ][$_]; + return 0 unless $self->{ans}[ $correct->[0] - 1 ][$_]{score}; } return 1; }; + $self->{checkTypes} = 'compatible' if $self->{checkTypes} && $self->{checkTypes} ne 'exact'; } if ($self->{allowBlankAnswers}) { @@ -522,13 +538,18 @@ sub perform_check { my ($self, $rh_ans) = @_; my $context = $self->context; $context->clearError; - my @correct; - my @student; + + my $checkTypes = $self->{checkTypes}; + $checkTypes = 'equal' if $checkTypes && $checkTypes ne 'compatible'; + # The answers for all parts are sent to the grader. The answers in the incorrect parts from # the correct answer may be used in the grader. The answers for each part are in a separate # list. The radio answer is sent as the numerical index of the choice. The count starts at # one, so that it is the location in the list sent to the grader of the answers for the # selected part (the radio answer is in position 0). + my @correct; + my @student; + push(@correct, $self->{correct} + 1); push(@student, $self->getIndexByValue($main::inputs_ref->{ $self->ANS_NAME(0) }) + 1); my $part_index = 1; @@ -542,9 +563,13 @@ sub perform_check { next if $student[0] != $part_index; return if $ans->{ans_message} ne '' || !defined $ans->{student_value}; return - if ($self->{checkTypes} - && $ans->{student_value}->type ne $ans->{correct_value}->type - && !($self->{allowBlankAnswers} && $ans->{student_ans} !~ m/\S/)); + if $checkTypes eq 'equal' + && $ans->{student_value}->type ne $ans->{correct_value}->type + && !($self->{allowBlankAnswers} && $ans->{student_ans} !~ m/\S/); + return + if $checkTypes eq 'compatible' + && !$ans->{correct_value}->typeMatch($ans->{student_value}, $ans) + && !($self->{allowBlankAnswers} && $ans->{student_ans} !~ m/\S/); } push(@correct, [@part_correct]); push(@student, [@part_student]); @@ -593,6 +618,7 @@ sub ANS_NAME { # Produce the label for a part of the radio answer. sub label { my ($self, $i) = @_; + $self->{originalLabels} //= $self->{labels}; return $self->{labels}[$i] if ref($self->{labels}) eq 'ARRAY' && $#{ $self->{labels} } >= $i; $self->{labels} = [ @main::ALPHABET[ 0 .. $#{ $self->{data} } ] ] if uc($self->{labels}) eq 'ABC'; @@ -638,6 +664,7 @@ sub generate_aria_label { # Produce the answer rule. sub ans_rule { my ($self, $size, @options) = @_; + $size ||= 20; my @data = @{ $self->{data} }; my @rules; @@ -705,18 +732,44 @@ sub ans_rule { push(@rules, $rule); } + return '' if !$self->{showInStatic} && ($main::displayMode eq 'TeX' || $main::displayMode eq 'PTX'); + + my $ptx_list_type = 'ul'; + my $ptx_sub_type = ' form="buttons"'; + if ($main::displayMode eq 'PTX') { + # Do we want an ol, ul, or dl? + if ($self->{displayLabels}) { + my $originalLabels = $self->{originalLabels}; + if (ref $originalLabels eq 'ARRAY') { + $ptx_list_type = 'dl'; + $ptx_sub_type = ' width = "narrow"'; + } elsif ($originalLabels =~ m/^(123|abc)$/i) { + my $marker = ''; + $marker = '1' if $originalLabels eq '123'; + $marker = 'a' if $originalLabels eq 'abc'; + $marker = 'A' if uc($originalLabels) eq 'ABC' && $originalLabels ne 'abc'; + $ptx_list_type = 'ol'; + $ptx_sub_type = qq( marker="$marker"); + } + } + } + return main::MODES( TeX => '\\begin{itemize}', - HTML => '
    ' + HTML => '
    ', + PTX => qq(<${ptx_list_type}${ptx_sub_type} name="$radio_name">) ) - . join(main::MODES(TeX => '\vskip\baselineskip', HTML => main::tag('div', style => 'margin-top:1rem')), @rules) - . main::MODES(TeX => '\\end{itemize}', HTML => '
    '); + . join( + main::MODES(TeX => '\vskip\baselineskip', HTML => main::tag('div', style => 'margin-top:1rem'), PTX => ''), + @rules + ) . main::MODES(TeX => '\\end{itemize}', HTML => '
    ', PTX => ""); } # Format a label. sub label_format { my ($self, $label) = @_; return '' unless $self->{displayLabels} && defined $label && $label ne ''; + return '' . $self->quoteXML($label) . '' if $main::displayMode eq 'PTX'; return sprintf($self->{labelFormat}, main::MODES(TeX => $self->quoteTeX($label), HTML => $self->quoteHTML($label))); } @@ -734,6 +787,8 @@ sub begin_radio { if ($extend) { main::EXTEND_RESPONSE($name, $name, $value, $checked) } else { $name = main::RECORD_ANS_NAME($name, { $value => $checked }) } + return '' if !$self->{showInStatic} && ($main::displayMode eq 'TeX' || $main::displayMode eq 'PTX'); + my $idSuffix = $extend ? "_$value" : ''; return main::MODES( @@ -754,7 +809,7 @@ sub begin_radio { $checked ? (checked => undef) : () ) . main::tag('label', for => "$name$idSuffix", $tag), - PTX => "
  • $tag", + PTX => ref $self->{originalLabels} eq 'ARRAY' ? "
  • $tag" : '
  • ', ); } diff --git a/macros/parsers/parserSolutionFor.pl b/macros/parsers/parserSolutionFor.pl index 25349e971b..8cf0f7cffb 100644 --- a/macros/parsers/parserSolutionFor.pl +++ b/macros/parsers/parserSolutionFor.pl @@ -2,7 +2,7 @@ =head1 NAME parserSolutionFor.pl - An answer checker that checks if a student's answer -satisifies an implicit equation. +satisfies an implicit equation. =head1 DESCRIPTION @@ -10,7 +10,7 @@ =head1 DESCRIPTION checks if a student's answer satisfies an implicit equation. We define a SolutionFor object class that lets you specify an equality that the student answer must satisfy, and a point that -DOES satify the equation. The overloaded == operator will +DOES satisfy the equation. The overloaded == operator will check if a given point satisfies the given equality. Use SolutionFor(equality,point[,options]) to create a SolutionFor object. @@ -151,7 +151,7 @@ sub compare { # # Set up a new context that is a copy of the current one, but # has the equality operator defined, and the SolutionFor object -# prededence set so that comparisons with points or numbers will +# precedence set so that comparisons with points or numbers will # be promoted to comparisons with the SolutionFor # sub getContext { diff --git a/macros/ui/PGchoicemacros.pl b/macros/ui/PGchoicemacros.pl index da948cfb10..c9cd8fb72c 100644 --- a/macros/ui/PGchoicemacros.pl +++ b/macros/ui/PGchoicemacros.pl @@ -92,7 +92,7 @@ =head2 new_match_list $ml = new Match(random(1,2000,1), ~~&std_print_q, ~~&std_print_a); -The first argument is the seed for the match list (choosen at random between 1 +The first argument is the seed for the match list (chosen at random between 1 and 2000 in the example above). The next two arguments are references to the print subroutines used to print the questions and the answers. Other printing methods can be used instead of the standard ones. An example of how to do this @@ -119,7 +119,7 @@ =head2 new_select_list $sl = new Select(random(1,2000,1), ~~&std_print_q, ~~&std_print_a); The parameters to the C class to see how to use this object to create a true/false question. @@ -168,7 +168,7 @@ =head2 new_multiple_choice $mc = new Multiple(random(1,2000,1), ~~&std_print_q, ~~&radio_print_a); The parameters to the C constructor are the same as those for the Match -constrcutor described above under C. +constructor described above under C. =cut diff --git a/macros/ui/PGinfo.pl b/macros/ui/PGinfo.pl index 276d3c33ce..0d26ceb013 100644 --- a/macros/ui/PGinfo.pl +++ b/macros/ui/PGinfo.pl @@ -15,7 +15,7 @@ =head2 listVariables Usage: listVariables(); Prints all variables submitted in the problem form and all variables in the -the Problem environment and all of the flag variables in Context(). +Problem environment and all of the flag variables in Context(). This is used for debugging and to determine the current context for the problem. diff --git a/macros/ui/alignedChoice.pl b/macros/ui/alignedChoice.pl index 4d19cdccd3..7fe91e7601 100644 --- a/macros/ui/alignedChoice.pl +++ b/macros/ui/alignedChoice.pl @@ -70,7 +70,7 @@ sub aligned_print_q { ###################################################################### # -# Genarate a new AlignedList object +# Generate a new AlignedList object # # $al = new_aligned_list(options) # diff --git a/macros/ui/niceTables.pl b/macros/ui/niceTables.pl index b13aeea805..f0709bc852 100644 --- a/macros/ui/niceTables.pl +++ b/macros/ui/niceTables.pl @@ -778,7 +778,7 @@ sub Rows { my $html = $row; # establish if this row has certain things - # when declared mulltiple times, last non-falsy values are used + # when declared multiple times, last non-falsy values are used my $bottom = 0; my $top = 0; my $rowcolor = ''; @@ -808,7 +808,7 @@ sub Rows { && ($bottom || $tableOpts->{horizontalrules})); # do cells in this row have a top or bottom border? - # although a propery of cells, LaTeX makes us do this at the row level + # although a property of cells, LaTeX makes us do this at the row level for my $x (@$rowArray) { $row = prefix($row, hrule($booktabs, 'cmid', $x->{top}) . "{$x->{leftcol}-$x->{rightcol}}", ' ') if ($i == 0 && $x->{top}); @@ -1514,8 +1514,7 @@ sub tag { $return .= $inner; $return .= "$separator"; } else { - $return .= '>' unless ($main::displayMode eq 'PTX'); - $return .= '/>' if ($main::displayMode eq 'PTX'); + $return .= ($main::displayMode eq 'PTX') ? '/>' : $name =~ /^(br|col|hr|img|input)$/ ? '>' : ">"; } return $return; } @@ -1588,7 +1587,7 @@ sub getWidthPercent { $unit = $2; } my %convert_to_pt = ( - # units with related absolute defintions + # units with related absolute definitions # the following are as TeX defines them pt => 1, pc => 12, @@ -1616,7 +1615,7 @@ sub getWidthPercent { sub hrule { my ($booktabs, $type, $thickness) = @_; if ($booktabs) { - my $thicknessArg = ''; + my $thicknessArg = $type eq 'bottom' ? '' : '{}'; $thicknessArg = '[' . getLaTeXthickness($thickness) . ']' if ($thickness); return "\\" . $type . 'rule' . $thicknessArg; diff --git a/macros/ui/problemPanic.pl b/macros/ui/problemPanic.pl index 40c539a328..027ef1ae72 100644 --- a/macros/ui/problemPanic.pl +++ b/macros/ui/problemPanic.pl @@ -89,7 +89,7 @@ =head1 DESCRIPTION Panic::GradeWithPenalty(); -in order to install the panic-button grader. You should do this afer +in order to install the panic-button grader. You should do this after setting the grader that you want to use for the problem itself, as the panic grader will use the one that is installed at the time the Panic::GradWithPenalty command is issued. @@ -99,14 +99,14 @@ =head1 DESCRIPTION sub _problemPanic_init { Panic::Init() } # -# The packge to contain the routines and data for the Panic buttons +# The package to contain the routines and data for the Panic buttons # package Panic; my $isTeX = 0; # true in hardcopy mode my $allowReset = 0; # true if a professor is viewing the problem my $buttonCount = 0; # number of panic buttons displayed so far -my @penalty = (0); # accummulated penalty values +my @penalty = (0); # accumulated penalty values my $grader; # problem's original grader # diff --git a/macros/ui/unionLists.pl b/macros/ui/unionLists.pl index 4ba214cd88..5189253dd4 100644 --- a/macros/ui/unionLists.pl +++ b/macros/ui/unionLists.pl @@ -54,7 +54,7 @@ =head1 unionLists.pl sub BeginList { my $LIST = 'OL'; - $LIST = shift if (uc($_[0]) eq "OL" or uc($_[0]) eq "UL"); + $LIST = uc(shift) if (defined($_[0]) && (uc($_[0]) eq "OL" or uc($_[0]) eq "UL")); my $enum = ($LIST eq 'OL' ? "enumerate" : "itemize"); my %options = @_; $LIST .= ' TYPE="' . $options{type} . '"' if defined($options{type}); diff --git a/sagecell-docker/Dockerfile b/sagecell-docker/Dockerfile new file mode 100644 index 0000000000..17cc411c35 --- /dev/null +++ b/sagecell-docker/Dockerfile @@ -0,0 +1,63 @@ +FROM sagemath/sagemath:10.8 + +USER root + +ENV DEBIAN_FRONTEND=noninteractive +ENV DEBCONF_NONINTERACTIVE_SEEN=true +ENV DEBCONF_NOWARNINGS=yes + +RUN apt-get update && apt-get install -y --no-install-recommends \ + automake \ + build-essential \ + curl \ + git \ + locales \ + openssh-server \ + python3 \ + python-is-python3 \ + python3-jupyter-client \ + python3-pip \ + python3-psutil \ + python3-zmq \ + rsyslog \ + wget \ + && echo "en_US.UTF-8 UTF-8" > /etc/locale.gen \ + && locale-gen \ + && curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \ + && apt-get install -y --no-install-recommends --no-install-suggests nodejs + +ENV SHELL=/bin/bash + +USER sage + +RUN echo "Installing SageCell server for SageMath" \ + && sage -pip install lockfile paramiko sockjs-tornado sqlalchemy \ + && git clone --single-branch --branch master --depth 1 https://github.com/sagemath/sagecell.git \ + && cd sagecell \ + && { sage -sh -c make || true; } \ + && sage -sh -c make \ + && rm -rf contrib doc tests .git \ + && cp config_default.py config.py \ + && sed -i 's/"username": None/"username": "sage"/' config.py \ + && sed -i 's/requires_tos = True/requires_tos = False/' config.py + +USER root + +COPY sagecell-entrypoint.sh /usr/local/bin/sagecell-entrypoint + +RUN echo "Configuring internal ssh server" \ + && chmod +x /usr/local/bin/sagecell-entrypoint \ + && echo "ListenAddress 127.0.0.1\nPasswordAuthentication no" \ + > /etc/ssh/sshd_config.d/sshd_config.conf + +RUN echo "Cleaning the Container" \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /tmp/* + +ENV PORT=3500 + +WORKDIR /home/sage/sagecell + +ENTRYPOINT ["sagecell-entrypoint"] +CMD ["sudo", "-H", "-E", "-u", "sage", "sage", "web_server.py", "-p $PORT"] diff --git a/sagecell-docker/README.md b/sagecell-docker/README.md new file mode 100644 index 0000000000..5a5a1ee2c6 --- /dev/null +++ b/sagecell-docker/README.md @@ -0,0 +1,47 @@ +# SageCell Docker Instructions + +These are instructions to build and use a docker image for running a SageCell service in production. + +## Build and Run the Docker Image + +Execute the following command from the `sagecell-docker` directory. + +```bash +docker build -t sagecell . +``` + +Then run the container by executing the following command in the `sagecell-docker` directory. + +```bash +docker run --rm -p 127.0.0.1:3500:3500 sagecell:latest +``` + +Note that the `-p` argument exposes port `3500` inside the container to port `3500` outside the container, but only +allows connections from localhost. This is important as it does not allow anyone else to use the SageCell service other +than the local webwork server. If a different port outside the container is needed, then change the first `3500` to the +desired port. Adding `--rm` removes the created volume for the container when it exits. Exit the container by typing +`Ctrl-C`. + +Access by specific external IP addresses is also possible if you want to have the SageCell service on another server. +The easiest way to accomplish this is by using an SSH tunnel. For example, execute +`ssh -L 3500:127.0.0.1:3550 userId@sagecell.server` where `userId` is your username on the SageCell server and +`sagecell.server` is the domain name or IP address of the SageCell server. There are other ways to accomplish this as +well, but the important thing is that access to the SageCell service be highly restricted as it allows untrusted code to +be executed. + +## Deploy the Image for Production Usage + +Copy the file `sagecell-docker.dist.service` to `sagecell-docker.service` and execute the following command from the +`sagecell-docker` directory to enable a `systemd` service for running the container. + +```bash +sudo systemctl enable $(pwd)/sagecell-docker.service +``` + +Then you can start the container by executing `sudo systemctl start sagecell-docker`, and stop the container by +executing `sudo systemctl stop sagecell-docker`. Note that any time the server is rebooted the service will start +automatically, so you usually do not need to execute those commands other than to start the service the first time after +enabling it. + +Note that the service simply executes the command give above for running the container directly. So modify the command +in the `sagecell-docker.service` file as described above if a different port is needed. diff --git a/sagecell-docker/sagecell-docker.dist.service b/sagecell-docker/sagecell-docker.dist.service new file mode 100644 index 0000000000..5638e09cc1 --- /dev/null +++ b/sagecell-docker/sagecell-docker.dist.service @@ -0,0 +1,11 @@ +[Unit] +Description=sagecell server +After=network.target + +[Service] +Type=simple +ExecStart=docker run --rm -p 127.0.0.1:3500:3500 sagecell:latest +KillMode=process + +[Install] +WantedBy=multi-user.target diff --git a/sagecell-docker/sagecell-entrypoint.sh b/sagecell-docker/sagecell-entrypoint.sh new file mode 100755 index 0000000000..77eed7bee1 --- /dev/null +++ b/sagecell-docker/sagecell-entrypoint.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +function configure_ssh() { + local key_file=/home/sage/.ssh/id_rsa + if [ $(ssh-add -l &> /dev/null; echo $?) -gt 0 ]; then + # Generate ssh keys for the user. + if [ ! -f $key_file ]; then + [ ! -d /home/sage/.ssh ] && mkdir /home/sage/.ssh + ssh-keygen -t rsa -f $key_file -P "" -q + fi + + # Start and configure the ssh-agent. + eval `ssh-agent` > /dev/null + local ssh_fingerprint=$(ssh-keygen -l -f $key_file | awk '{print $2}') + if [ ! -z "$ssh_fingerprint" ] && [ -z "$(ssh-add -l | grep "$ssh_fingerprint")" ]; then + ssh-add $key_file 2> /dev/null + fi + + # Add localhost to known_hosts. + [ ! -f /home/sage/.ssh/known_hosts ] && touch /home/sage/.ssh/known_hosts + if [ -z "$(ssh-keygen -F localhost)" ]; then + ssh-keyscan -H localhost > /home/sage/.ssh/known_hosts 2> /dev/null + fi + + cat ${key_file}.pub > /home/sage/.ssh/authorized_keys + fi +} + +function check_ssh() { + ssh -v -o PreferredAuthentications=publickey -o BatchMode=yes -o ConnectTimeout=10 \ + -o StrictHostKeyChecking=no localhost /bin/true &> /dev/null + return $? +} + +# Configure and start rsyslog. +sed -i '/imklog/s/^/#/' /etc/rsyslog.conf +rsyslogd + +# Start and configure the ssh server for internal use. +service ssh start > /dev/null + +export -f configure_ssh +export -f check_ssh + +su sage bash -c "configure_ssh" +su sage bash -c "check_ssh" + +if [ $? -eq 0 ]; then + echo "Executing: $(eval "echo $@")" + exec $(eval "echo $@") +else + echo "ERROR: SageCell server not started. Failed to configure internal ssh server." +fi diff --git a/t/README.md b/t/README.md index 2cf778d150..41d600c3cb 100644 --- a/t/README.md +++ b/t/README.md @@ -16,7 +16,7 @@ and a working test can be better than documentation because it shows how the code currently works in practice. Old references can be found on the WebWork wiki page -[Unit Testing](https://webwork.maa.org/wiki/Unit_Testing) +[Unit Testing](https://wiki.openwebwork.org/wiki/Unit_Testing) ## Unit Tests @@ -31,8 +31,8 @@ Best practice is to create a directory for each module being tested and group similar tests together in separate files with a descriptive name, such as **t/units/** for testing the **Units.pm** module. -Formal unit tests are located in the the `macros` and `contexts` directories -that are designed to test the pg macros and contexts respectively. +Formal unit tests are located in the `macros` and `contexts` directories that +are designed to test the pg macros and contexts respectively. ## Running the tests diff --git a/t/contexts/toltype_digits.t b/t/contexts/toltype_digits.t index baa7276a18..ef2102a490 100644 --- a/t/contexts/toltype_digits.t +++ b/t/contexts/toltype_digits.t @@ -16,7 +16,7 @@ loadMacros('PGstandard.pl', 'MathObjects.pl'); my $ctx = Context('Numeric'); my $pi = Real('pi'); -subtest 'set tolTrunction to 1' => sub { +subtest 'set tolTruncation to 1' => sub { $ctx->flags->set(tolType => 'digits', tolerance => 3, tolTruncation => 1); is check_score($pi, Compute('3.14')), 1, 'toltype digits: pi is 3.14'; @@ -26,7 +26,7 @@ subtest 'set tolTrunction to 1' => sub { is check_score($pi, Compute('3.15')), 0, 'toltype digits: pi is not 3.15'; }; -subtest 'set tolTrunction to 0' => sub { +subtest 'set tolTruncation to 0' => sub { $ctx->flags->set(tolType => 'digits', tolerance => 3, tolTruncation => 0); is check_score($pi, Compute('3.14')), 1, 'toltype digits: pi is 3.14'; diff --git a/t/contexts/trig_degrees.t b/t/contexts/trig_degrees.t index 3e70c8683c..51e0723402 100644 --- a/t/contexts/trig_degrees.t +++ b/t/contexts/trig_degrees.t @@ -18,7 +18,7 @@ my $ctx = Context('TrigDegrees'); ok(Value::isContext($ctx), 'trig degrees: check context'); ok my $cos60 = Compute('cos(60)'), 'Call Compute'; -ok my $eval_cos60 = $cos60->cmp->evaluate('1/2'), 'evalute an answer to cos(60)'; +ok my $eval_cos60 = $cos60->cmp->evaluate('1/2'), 'evaluate an answer to cos(60)'; is $eval_cos60, hash { field type => 'Value (Real)'; diff --git a/t/macros/load_macros.t b/t/macros/load_macros.t index 59d37ed799..c8ab7e6575 100644 --- a/t/macros/load_macros.t +++ b/t/macros/load_macros.t @@ -33,8 +33,7 @@ my %baseMacros = ( 'PGcommonFunctions.pl' => 1 ); -# PG_CAPAmacros.pl is not really broken, but it depends on files that are in the OPL and not in the PG repository. -my %brokenMacros = ('answerDiscussion.pl' => 1, 'PG_CAPAmacros.pl' => 1); +my %brokenMacros = ('answerDiscussion.pl' => 1); # Find all macro files inside the $ENV{PG_ROOT}/macros directory. my @macro_files; diff --git a/t/macros/numerical_methods.t b/t/macros/numerical_methods.t index b5a00a2a31..bfcbd89635 100644 --- a/t/macros/numerical_methods.t +++ b/t/macros/numerical_methods.t @@ -101,8 +101,8 @@ subtest 'Quadrature' => sub { is trapezoid($f, 0, 2, steps => 4), 2.75, 'Trapezoid rule of x^2 on [0,2]'; - is romberg($f, 0, 2), 8 / 3, 'Romberg interation for x^2 on [0,2]'; - is romberg($g, 0, 1), exp(1) - 1, 'Romberg interation on e^x on [0,1]'; + is romberg($f, 0, 2), 8 / 3, 'Romberg integration for x^2 on [0,2]'; + is romberg($g, 0, 1), exp(1) - 1, 'Romberg integration on e^x on [0,1]'; is inv_romberg($g, 0, exp(1) - 1), 1.0, 'Inverse Romberg to find b with int of e^x on [0,b] returns 1'; }; diff --git a/t/macros/pgaux.t b/t/macros/pgaux.t index f6977ea347..3b29839c3b 100644 --- a/t/macros/pgaux.t +++ b/t/macros/pgaux.t @@ -13,105 +13,106 @@ do "$ENV{PG_ROOT}/t/build_PG_envir.pl"; loadMacros('PGauxiliaryFunctions.pl'); -# test step functions - -is(step(8), 1, "step: positive number"); -is(step(-8), 0, "step: negative number"); -is(step(0), 0, "step: step(0)=0"); - -# test floor function - -is(floor(0.5), 0, "floor: positive non-integer"); -is(floor(-0.5), -1, "floor: negative non-integer"); -is(floor(1), 1, "floor: positive integer"); -is(floor(0), 0, "floor: floor(0)=0"); -is(floor(-1), -1, "floor: negative integer"); - -# test ceiling function - -is(ceil(0.5), 1, "ceil: positive non-integer"); -is(ceil(-0.5), 0, "ceil: negative non-integer"); -is(ceil(1), 1, "ceil: positive integer"); -is(ceil(0), 0, "ceil: floor(0)=0"); -is(ceil(-1), -1, "ceil: negative integer"); - -# max/min functions - -is(max(1, 2, 3, 9, 4, 5, 6, 8), 9, "max: set of integers"); -is(max(0.1, -2.3, 1.345, 2.71712, -1000.1), 2.71712, "max: set of decimals"); -is(min(1, 2, 3, 9, 4, 5, 6, 8), 1, "min: set of integers"); -is(min(0.1, -2.3, 1.345, 2.71712, -1000.1), -1000.1, "min: set of decimals"); - -# round function - -is(round(0.95), 1, "round: fractional part > 0.5"); -is(round(0.45), 0, "round: fractional part < 0.5"); -is(round(0.5), 1, "round: fractional part = 0.5"); -is(round(-0.95), -1, "round: fractional part > 0.5 and negative"); -is(round(-0.45), 0, "round: fractional part < 0.5 and negative"); -is(round(-0.5), -1, "round: fractional part = 0.5 and negative"); - -# Round function which takes a second number, the number of digits to round to - -is(Round(1.793, 2), 1.79, "Round to 2 digits: test 1"); -is(Round(1.797, 2), 1.80, "Round to 2 digits: test 2"); -is(Round(1.795, 2), 1.80, "Round to 2 digits: test 3"); -is(Round(-1.793, 2), -1.79, "Round to 2 digits: test 1"); -is(Round(-1.797, 2), -1.80, "Round to 2 digits: test 2"); -is(Round(-1.795, 2), -1.80, "Round to 2 digits: test 3"); - -is(Round(15.793, -1), 20, "Round to -1 digits (nearest 10)"); - -# lcm - -is(lcm(20, 30), 60, "lcm: non relatively prime numbers"); -is(lcm(5, 6), 30, "lcm: relatively prime numbers"); -is(lcm(2, 3, 4), 12, "lcm: 3 numbers"); -is(lcm(2, 3, 4, 5, 6, 7, 8), 840, "lcm: 7 numbers"); - -# gcd -is(gcd(16, 8), 8, "gcd: 2 powers of 2"); -is(gcd(10, 9), 1, "gcd: 2 relatively prime"); - -is(gcd(10, 20, 30, 40), 10, "gcd: 4 multiples of 10"); - -# isPrime -is(isPrime(7), 1, "isPrime: 7 is prime"); -is(isPrime(2), 1, "isPrime: 2 is prime"); -is(isPrime(15), 0, "isPrime: 15 is not prime"); +subtest 'Step function' => sub { + is(step(8), 1, "step: positive number"); + is(step(-8), 0, "step: negative number"); + is(step(0), 0, "step: step(0)=0"); +}; + +subtest 'Floor function' => sub { + is(floor(0.5), 0, "floor: positive non-integer"); + is(floor(-0.5), -1, "floor: negative non-integer"); + is(floor(1), 1, "floor: positive integer"); + is(floor(0), 0, "floor: floor(0)=0"); + is(floor(-1), -1, "floor: negative integer"); +}; + +subtest 'Ceiling function' => sub { + is(ceil(0.5), 1, "ceil: positive non-integer"); + is(ceil(-0.5), 0, "ceil: negative non-integer"); + is(ceil(1), 1, "ceil: positive integer"); + is(ceil(0), 0, "ceil: floor(0)=0"); + is(ceil(-1), -1, "ceil: negative integer"); +}; + +subtest 'Min and Max functions' => sub { + is(max(1, 2, 3, 9, 4, 5, 6, 8), 9, "max: set of integers"); + is(max(0.1, -2.3, 1.345, 2.71712, -1000.1), 2.71712, "max: set of decimals"); + is(min(1, 2, 3, 9, 4, 5, 6, 8), 1, "min: set of integers"); + is(min(0.1, -2.3, 1.345, 2.71712, -1000.1), -1000.1, "min: set of decimals"); +}; + +subtest 'round and Round functions' => sub { + is(round(0.95), 1, "round: fractional part > 0.5"); + is(round(0.45), 0, "round: fractional part < 0.5"); + is(round(0.5), 1, "round: fractional part = 0.5"); + is(round(-0.95), -1, "round: fractional part > 0.5 and negative"); + is(round(-0.45), 0, "round: fractional part < 0.5 and negative"); + is(round(-0.5), -1, "round: fractional part = 0.5 and negative"); + + # Round function which takes a second number, the number of digits to round to + + is(Round(1.793, 2), 1.79, "Round to 2 digits: test 1"); + is(Round(1.797, 2), 1.80, "Round to 2 digits: test 2"); + is(Round(1.795, 2), 1.80, "Round to 2 digits: test 3"); + is(Round(-1.793, 2), -1.79, "Round to 2 digits: test 1"); + is(Round(-1.797, 2), -1.80, "Round to 2 digits: test 2"); + is(Round(-1.795, 2), -1.80, "Round to 2 digits: test 3"); + + is(Round(15.793, -1), 20, "Round to -1 digits (nearest 10)"); + + is(Round(134.49999999999997, 0), 134.0, 'Round a number with decimals close to 0.5'); + is(Round(1.49999999999991, 0), 1.0, 'Round another number with decimals close to 0.5'); + is(Round(0.01499999999991, 2), 0.01, 'Round a number close to 0.005 to 2 digits'); +}; + +subtest 'lcm and gcd functions' => sub { + is(lcm(20, 30), 60, "lcm: non relatively prime numbers"); + is(lcm(5, 6), 30, "lcm: relatively prime numbers"); + is(lcm(2, 3, 4), 12, "lcm: 3 numbers"); + is(lcm(2, 3, 4, 5, 6, 7, 8), 840, "lcm: 7 numbers"); + + # gcd + is(gcd(16, 8), 8, "gcd: 2 powers of 2"); + is(gcd(10, 9), 1, "gcd: 2 relatively prime"); + is(gcd(10, 20, 30, 40), 10, "gcd: 4 multiples of 10"); +}; + +subtest 'isPrime function' => sub { + is(isPrime(7), 1, "isPrime: 7 is prime"); + is(isPrime(2), 1, "isPrime: 2 is prime"); + is(isPrime(15), 0, "isPrime: 15 is not prime"); +}; # random_coprime - -my $sum = 0; -for my $i (1 .. 100) { - my @coprimes = random_coprime([ 1 .. 20 ], [ 1 .. 20 ]); - $sum += gcd($coprimes[0], $coprimes[1]); -} -is($sum, 100, "random_coprime: 100 tests in 1..20,1..20"); - -$sum = 0; - -for my $i (1 .. 100) { - my @coprimes = random_coprime([ -9 .. -1, 1 .. 9 ], [ 1 .. 9 ], [ 1 .. 9 ]); - $sum += gcd(@coprimes); -} -is($sum, 100, "random_coprime: 100 tests in [-9..-1,1..9],[1..9],[1..9]"); - -my ($sum1, $sum2, $sum3, $sum4) = (0, 0, 0); -for my $i (1 .. 100) { - my @coprimes = random_pairwise_coprime([ -9 .. -1, 1 .. 9 ], [ 1 .. 9 ], [ 1 .. 9 ]); - $sum1 += gcd(@coprimes); - $sum2 += gcd($coprimes[0], $coprimes[1]); - $sum3 += gcd($coprimes[0], $coprimes[2]); - $sum4 += gcd($coprimes[1], $coprimes[2]); -} -is($sum1 + $sum2 + $sum3 + $sum4, 400, "random_pairwise_coprime: 100 tests of [-9..-1,1..9],[1..9],[1..9]"); - -# reduce -# it would be nicer to directly compare the arrays -my @my_arr = (3, 4); -my @res = reduce(15, 20); -is($my_arr[0], $res[0], "reduce: correct numerator"); -is($my_arr[1], $res[1], "reduce: correct denominator"); +subtest 'random_coprime function' => sub { + my $sum = 0; + for my $i (1 .. 100) { + my @coprimes = random_coprime([ 1 .. 20 ], [ 1 .. 20 ]); + $sum += gcd($coprimes[0], $coprimes[1]); + } + is($sum, 100, "random_coprime: 100 tests in 1..20,1..20"); + + $sum = 0; + for my $i (1 .. 100) { + my @coprimes = random_coprime([ -9 .. -1, 1 .. 9 ], [ 1 .. 9 ], [ 1 .. 9 ]); + $sum += gcd(@coprimes); + } + is($sum, 100, "random_coprime: 100 tests in [-9..-1,1..9],[1..9],[1..9]"); + + my ($sum1, $sum2, $sum3, $sum4) = (0, 0, 0); + for my $i (1 .. 100) { + my @coprimes = random_pairwise_coprime([ -9 .. -1, 1 .. 9 ], [ 1 .. 9 ], [ 1 .. 9 ]); + $sum1 += gcd(@coprimes); + $sum2 += gcd($coprimes[0], $coprimes[1]); + $sum3 += gcd($coprimes[0], $coprimes[2]); + $sum4 += gcd($coprimes[1], $coprimes[2]); + } + is($sum1 + $sum2 + $sum3 + $sum4, 400, "random_pairwise_coprime: 100 tests of [-9..-1,1..9],[1..9],[1..9]"); +}; + +subtest 'reduce function' => sub { + is([ reduce(15, 20) ], [ 3, 4 ], "reduce gives correct numerator and denominator"); +}; done_testing; diff --git a/t/math_objects/matrix.t b/t/math_objects/matrix.t index dfdc890c35..a712dce833 100644 --- a/t/math_objects/matrix.t +++ b/t/math_objects/matrix.t @@ -180,10 +180,92 @@ subtest 'Test if Matrix is in (R)REF' => sub { ok !$B4->isRREF, "$B4 is not in RREF"; }; +subtest 'Check if two matrices are (fuzzy) row equivalent' => sub { + my $A1 = Matrix(1, 2, 3); + my $A2 = Matrix([ 1, 2, 3 ], [ 4, 5, 6 ]); + my $A3 = Matrix([ [ 1, 2 ], [ 3, 4 ] ], [ [ 5, 6 ], [ 7, 8 ] ]); + my $B1 = Matrix(2, 4, 6); + my $B2 = Matrix([ 1, 2 ], [ 3, 4 ], [ 5, 6 ]); + my $C1 = Matrix(0, 4, 6); + my $Z1 = Matrix([ 0, 0, 0 ], [ 0, 0, 0 ]); + my $Z2 = Matrix([ 0, 0, 0 ], [ 0, 0, 10**-14 ]); + + my $M1 = Matrix([ 3, 1 ], [ 1, 1 / 3 ]); + my $M2 = Matrix([ 3, 1 ], [ 0, 0 ]); + my $M3 = Matrix([ 1, 0.3333 ], [ 0, 0 ]); + my $M4 = Matrix([ 1, 0.33 ], [ 0, 0 ]); + my $M5 = Matrix([ 6, 2 ], [ 9, 3 ]); + my $M6 = Matrix([ 3, 1 ], [ 1, 3 ]); + + my $N1 = Matrix([ [ 1, 2 ], [ 2, 4 ] ], [ [ 1, 2 ], [ 3, 4 ] ]); + my $N2 = Matrix([ [ 3, 6 ], [ 0, 0 ] ], [ [ 1, 0 ], [ 0, 1 ] ]); + my $N3 = Matrix([ [ 3, 6 ], [ 1, 0 ] ], [ [ 1, 0 ], [ 0, 1 ] ]); + + ok $Z1->isREQ($Z2), 'Zero matrices are row equivalent'; + ok !$Z1->isREQ($A2), 'A zero matrix is not row equivalent to a nonzero matrix'; + ok $A1->isREQ($B1), 'Parallel degree 1 matrices are row equivalent'; + ok !$C1->isREQ($B1), 'Non-parallel degree 1 matrices are not row equivalent'; + ok $M2->isREQ($M5), 'Row equivalent matrices are row equivalent'; + ok $M2->isREQ($M1), 'Row equivalent matrices are row equivalent, even with some machine rounding'; + ok $M2->isREQ($M3), 'Row equivalent matrices are row equivalent, even with student rounding'; + ok !$M2->isREQ($M4), 'Matrices are not row equivalent if rounding is too much'; + ok !$M2->isREQ($M6), 'Non-row equivalent matrices are not row equivalent'; + ok $N1->isREQ($N2), 'Row equivalent matrices are row equivalent, even at degree above 2'; + ok !$N1->isREQ($N3), 'Non-row equivalent matrices are not row equivalent, even at degree above 2'; + + like dies { + $A2->isREQ($A3); + }, qr/Cannot compare row equivalency of matrices of different degree/, + 'Test that error is thrown for comparing matrices of differing degrees'; + like dies { + $A2->isREQ($B2); + }, qr/Cannot compare row equivalency because matrices differ in the first dimension/, + 'Test that error is thrown for comparing matrices of differing dimensioon'; + +}; + +subtest 'Normalized Gram Schmidt' => sub { + my @A = ([ 1, 2, 2 ], [ 2, 2, 1 ]); + my $A = Matrix(@A); + my @NGSA = $A->NGS; + my @NGSAc = $A->NGS(cols => 1); + my @NGSVMA = Value::Matrix->NGS(@A); + my $NGSA = $A->NGS; + my $NGSAc = $A->NGS(cols => 1); + my $NGSVMA = Value::Matrix->NGS(@A); + + is "@NGSA", "[0.333333,0.666667,0.666667] [0.808452,0.16169,-0.565916]", + 'Test that NGS finds a correct normalized GS basis'; + is "@NGSAc", "[[0.447214],[0.894427]] [[0.894427],[-0.447214]]", + 'Test that NGS finds a correct normalized GS basis'; + is "@NGSVMA", "[0.333333,0.666667,0.666667] [0.808452,0.16169,-0.565916]", + 'Test that NGS finds a correct normalized GS basis'; + is "$NGSA", "[[0.333333,0.666667,0.666667],[0.808452,0.16169,-0.565916]]", + 'Test that NGS finds a correct normalized GS basis'; + is "$NGSAc", "[[0.447214,0.894427],[0.894427,-0.447214]]", 'Test that NGS finds a correct normalized GS basis'; + is "$NGSVMA", "[[0.333333,0.666667,0.666667],[0.808452,0.16169,-0.565916]]", + 'Test that NGS finds a correct normalized GS basis'; + + like dies { + Value::Matrix->NGS(); + }, qr/You must provide vectors to apply Gram Schmidt to/, 'Test that you must pass something as an argument'; + like dies { + Value::Matrix->NGS([ 0, 0 ], [ 0, 10**-16 ]); + }, qr/You must provide at least one nonzero row for Gram Schmidt/, + 'Test that you must pass at least one nonzero row'; + like dies { + Value::Matrix->NGS([ [ 1, 0 ], [ 1, 0 ] ]); + }, qr/Rows provided for Gram Schmidt should not be nested arrays/, + 'Test that rows are rows, not nested matrices'; + like dies { + Value::Matrix->NGS([ 1, 0 ], [ 1, 0, 0 ]); + }, qr/All rows provided for Gram Schmidt should have the same length/, 'Test that rows have same length'; +}; + subtest 'Transpose a Matrix' => sub { my $A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]); my $B = Matrix([ [ 1, 5, 9 ], [ 2, 6, 10 ], [ 3, 7, 11 ], [ 4, 8, 12 ] ]); - is $A->transpose->TeX, $B->TeX, 'Test the tranpose of a matrix'; + is $A->transpose->TeX, $B->TeX, 'Test the transpose of a matrix'; my $row = Matrix([ 1, 2, 3, 4 ]); my $row_trans = Matrix([ [1], [2], [3], [4] ]); @@ -191,7 +273,7 @@ subtest 'Transpose a Matrix' => sub { my $C = Matrix([ [ [ 1, 2 ], [ 3, 4 ] ], [ [ 5, 6 ], [ 7, 8 ] ] ]); my $D = Matrix([ [ [ 1, 3 ], [ 2, 4 ] ], [ [ 5, 7 ], [ 6, 8 ] ] ]); - is $C->transpose->TeX, $D->TeX, 'Test the tranpose of a degree 3 tensor'; + is $C->transpose->TeX, $D->TeX, 'Test the transpose of a degree 3 tensor'; }; subtest 'Extract an element' => sub { @@ -215,6 +297,165 @@ subtest 'Extract a column' => sub { }, qr/Column must be a positive integer/, 'Test that an error is thrown for passing a non-positive integer'; }; +subtest 'Replace a value' => sub { + my $A = Matrix([ 1, 2, 3 ]); + my $B = Matrix([ [ 1, 2, 3 ], [ 4, 5, 6 ] ]); + my $C = Matrix([ [ 1, 2, 3 ], [ 4, 5, 6 ] ]); + my $D = Matrix([ [ [ 1, 2 ], [ 3, 4 ] ], [ [ 5, 6 ], [ 7, 8 ] ] ]); + + is $A->replace(9, [2]), 2, 'Replace an element from a degree 1 matrix, returning replaced element'; + is $A->TeX, Matrix([ 1, 9, 3 ])->TeX, 'Replace an element from a degree 1 matrix'; + is $B->replace(9, [ 2, 1 ]), 4, 'Replace an element from a degree 2 matrix, returning replaced element'; + is $B->TeX, Matrix([ [ 1, 2, 3 ], [ 9, 5, 6 ] ])->TeX, 'Replace an element from a degree 2 matrix'; + is $C->replace(9, 2, 1), 4, 'Replace an element from a degree 2 matrix, returning replaced element'; + is $C->TeX, Matrix([ [ 1, 2, 3 ], [ 9, 5, 6 ] ])->TeX, 'Replace an element from a degree 2 matrix'; + is $D->replace(9, [ 2, 1, 2 ]), 6, 'Replace an element from a degree 3 matrix, returning replaced element'; + is $D->TeX, Matrix([ [ [ 1, 2 ], [ 3, 4 ] ], [ [ 5, 9 ], [ 7, 8 ] ] ])->TeX, + 'Replace an element from a degree 3 matrix'; + + like dies { + $B->replace(10, [ 1, 2, 3 ]); + }, qr/There should be 2 indices/, 'Check correct number of indices passed'; + like dies { + $B->replace(10, [ 3, 1 ]); + }, qr/The first index is outside of the array bounds/, 'Check index is within bounds'; + like dies { + $B->replace(Point(1, 2), [ 2, 1 ]); + }, qr/The new entry value should be a Number not a Point/, 'Check replaced value has the same type'; + like dies { + $B->replace(Formula('x'), [ 2, 1 ]); + }, qr/Cannot replace a matrix entry with a Formula/, 'Check replaced value is not a Formula'; +}; + +subtest 'Submatrix' => sub { + my $A = Matrix([ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ]); + is $A->subMatrix([ 2 .. 3 ], [ 2 .. 4 ])->TeX, Matrix([ 6, 7, 8 ], [ 10, 11, 12 ])->TeX, + 'Submatrix from specifying rows/cols to keep'; + is $A->subMatrix(2, 3)->TeX, Matrix([ 1, 2, 4 ], [ 9, 10, 12 ])->TeX, + 'Submatrix from specifying row/col to remove'; + is $A->subMatrix([1], 0)->TeX, Matrix([ [ 1, 2, 3, 4 ] ])->TeX, + 'Submatrix from specifying rows/cols with mixed syntax'; + is $A->subMatrix([ 3, 1, 2 ], [ 1, 4, 2 ])->TeX, Matrix([ 9, 12, 10 ], [ 1, 4, 2 ], [ 5, 8, 6 ])->TeX, + 'Submatrix from permuting rows and columns, droppping one column'; + + my $B = Matrix(2, 4, 6, 8); + is $B->subMatrix([ 1, 3 ])->TeX, Matrix(2, 6)->TeX, + 'Submatrix of degree 1 matrix from specifying entries to keep'; + is $B->subMatrix(2)->TeX, Matrix(2, 6, 8)->TeX, 'Submatrix of degree 1 matrix from specifying entry to remove'; + + my $C = Matrix([ [ 1, 2, 3 ], [ 4, 5, 6 ] ], [ [ 7, 8, 9 ], [ 10, 11, 12 ] ]); + is $C->subMatrix(0, 1, [ 1, 3 ])->TeX, Matrix([ [ 4, 6 ] ], [ [ 10, 12 ] ])->TeX, + 'Submatrix of degree 3 matrix from specifying indices to keep'; + is $C->subMatrix(1, 2, 3)->TeX, Matrix([ [ [ 7, 8 ] ] ])->TeX, + 'Submatrix of degree 3 matrix from specifying indices to remove'; + + like dies { + $A->subMatrix(1, 1, 1); + }, qr/There must be 2 arguments/, 'check that error is thrown for too many arguments.'; + like dies { + $A->subMatrix(1); + }, qr/There must be 2 arguments/, 'check that error is thrown for too few arguments.'; + + like dies { + $A->subMatrix(-1, 2); + }, qr/The input -1 is not a valid index/, 'check that error is thrown for an invalid integer argument.'; + like dies { + $A->subMatrix(1.5, 2); + }, qr/The input 1\.5 is not a valid index/, 'check that error is thrown for an invalid integer argument.'; + like dies { + $A->subMatrix(1, 5); + }, qr/The input 5 is not a valid index/, 'check that error is thrown for an invalid integer argument.'; + like dies { + $A->subMatrix(1, 'a'); + }, qr/The input a is not a valid index/, 'check that error is thrown for an invalid integer argument.'; + like dies { + $A->subMatrix(1, []); + }, qr/Cannot use empty array reference for indices to keep/, + 'check that error is thrown for empty array reference argument.'; + like dies { + $A->subMatrix([ 1, 1.1, 2 ], [ 2, 3 ]); + }, qr/The input 1\.1 is not a valid index/, 'check that error is thrown for an non integer row.'; + like dies { + $A->subMatrix([ 1, 2 ], [ 2.5, 3 ]); + }, qr/The input 2\.5 is not a valid index/, 'check that error is thrown for an non integer column.'; + like dies { + $A->subMatrix([ 0, 1 ], [ 2, 3 ]); + }, qr/The input 0 is not a valid index/, 'check that error is thrown for zero in an array ref.'; + like dies { + $A->subMatrix([ -2, 1 ], [ 2, 3 ]); + }, qr/The input -2 is not a valid index/, + 'check that error is thrown for integer in an array ref that is out of bounds.'; + like dies { + $A->subMatrix([ 5, 1 ], [ 2, 3 ]); + }, qr/The input 5 is not a valid index/, + 'check that error is thrown for integer in an array ref that is out of bounds.'; +}; + +subtest 'Remove row' => sub { + my $A = Matrix([ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ], [ 13, 14, 15, 16 ]); + is $A->removeRow(3)->TeX, Matrix([ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 13, 14, 15, 16 ])->TeX, + 'Remove a row from a degree 2 Matrix'; + + my $B = Matrix([ [ 1, 2, 3 ], [ 4, 5, 6 ] ], [ [ 7, 8, 9 ], [ 10, 11, 12 ] ]); + is $B->removeRow(2)->TeX, Matrix([ [ [ 1, 2, 3 ], [ 4, 5, 6 ] ] ])->TeX, 'Remove a row from a degree 3 Matrix'; + + my $C = Matrix(1, 2, 3); + like dies { + $C->removeRow(1); + }, qr/cannot be used on a Matrix of degree 1/, + 'check that error is thrown if removeRow used on degree 1 Matrix'; + like dies { + $A->removeRow(0); + }, qr/Can only remove rows 1 through 4/, 'check that error is thrown for bad row specification'; + like dies { + $A->removeRow(5); + }, qr/Can only remove rows 1 through 4/, 'check that error is thrown for bad row specification'; + like dies { + $A->removeRow(1.5); + }, qr/Can only remove rows 1 through 4/, 'check that error is thrown for bad row specification'; + like dies { + $A->removeRow('a'); + }, qr/Can only remove rows 1 through 4/, 'check that error is thrown for bad row specification'; + + my $D = Matrix([ [ 1, 2, 3 ] ]); + like dies { + $D->removeRow(1); + }, qr/cannot remove a Matrix's only row/, 'check that error is thrown for trying to remove the only row'; +}; + +subtest 'Remove column' => sub { + my $A = Matrix([ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ], [ 13, 14, 15, 16 ]); + is $A->removeColumn(3)->TeX, Matrix([ 1, 2, 4 ], [ 5, 6, 8 ], [ 9, 10, 12 ], [ 13, 14, 16 ])->TeX, + 'Remove a column from a degree 2 Matrix'; + + my $B = Matrix([ [ 1, 2, 3 ], [ 4, 5, 6 ] ], [ [ 7, 8, 9 ], [ 10, 11, 12 ] ]); + is $B->removeColumn(2)->TeX, Matrix([ [ 1, 2, 3 ] ], [ [ 7, 8, 9 ] ])->TeX, + 'Remove a column from a degree 3 Matrix'; + + my $C = Matrix(1, 2, 3); + like dies { + $C->removeColumn(1); + }, qr/cannot be used on a Matrix of degree 1/, + 'check that error is thrown if removeColumn used on degree 1 Matrix'; + like dies { + $A->removeColumn(0); + }, qr/Can only remove columns 1 through 4/, 'check that error is thrown for bad column specification'; + like dies { + $A->removeColumn(5); + }, qr/Can only remove columns 1 through 4/, 'check that error is thrown for bad column specification'; + like dies { + $A->removeColumn(1.5); + }, qr/Can only remove columns 1 through 4/, 'check that error is thrown for bad column specification'; + like dies { + $A->removeColumn('a'); + }, qr/Can only remove columns 1 through 4/, 'check that error is thrown for bad column specification'; + + my $D = Matrix([1], [2], [3]); + like dies { + $D->removeColumn(1); + }, qr/cannot remove a Matrix's only column/, 'check that error is thrown for trying to remove the only column'; +}; + subtest 'Construct an identity matrix' => sub { my $I = Value::Matrix->I(3); my $B = Matrix([ [ 1, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ] ]); @@ -226,7 +467,7 @@ subtest 'Construct an identity matrix' => sub { subtest 'Construct a permutation matrix' => sub { my $P1 = Value::Matrix->P(3, [ 1, 2, 3 ]); - is $P1->TeX, Matrix([ [ 0, 0, 1 ], [ 1, 0, 0 ], [ 0, 1, 0 ] ])->TeX, 'Create permuation matrix on cycle (123)'; + is $P1->TeX, Matrix([ [ 0, 0, 1 ], [ 1, 0, 0 ], [ 0, 1, 0 ] ])->TeX, 'Create permutation matrix on cycle (123)'; my $P2 = Value::Matrix->P(6, [ 1, 3 ], [ 2, 4, 6 ]); is $P2->TeX, @@ -327,8 +568,8 @@ subtest 'Multiply matrices' => sub { my $prod1 = Matrix([ [ -8, 2, -6 ], [ -17, 8, -15 ], [ -26, 14, -24 ] ]); my $C = Matrix([ [ 1, -5, -2, -5 ], [ 0, -5, 5, -4 ], [ 4, 1, -1, 1 ] ]); my $prod2 = Matrix([ [ 13, -12, 5, -10 ], [ 28, -39, 11, -34 ], [ 43, -66, 17, -58 ] ]); - ok $A*$B == $prod1, 'Checking the product of two 3 by 3 matrices'; - ok $A*$C == $prod2, 'Checking the product of a 3 by 3 and 3 by 4 matrix'; + ok $A * $B == $prod1, 'Checking the product of two 3 by 3 matrices'; + ok $A * $C == $prod2, 'Checking the product of a 3 by 3 and 3 by 4 matrix'; like dies { $C * $A }, qr/Matrices of dimensions \d+x\d+ and \d+x\d+ can't be multiplied/, 'Test that multiplying row matrices of incompatible dimsensions throws an error'; @@ -337,15 +578,16 @@ subtest 'Multiply matrices' => sub { my $row = Matrix(1, 2, 3); my $prod3 = Matrix(14, 32, 50); - ok $A*$row == $prod3, 'Multiply a 3 by 3 matrix and a row matrix of length 3 (the row is promoted to a matrix)'; + ok $A * $row == $prod3, + 'Multiply a 3 by 3 matrix and a row matrix of length 3 (the row is promoted to a matrix)'; my $col = Matrix([ [1], [2], [3] ]); my $prod4 = Matrix([ [14], [32], [50] ]); - ok $A*$col == $prod4, 'Multiply a 3 by 3 matrix and a column matrix of length 3'; + ok $A * $col == $prod4, 'Multiply a 3 by 3 matrix and a column matrix of length 3'; my $v = Vector(1, 2, 3); my $prod5 = Vector(14, 32, 50); - ok $A*$v == $prod5, 'Multiply a 3 by 3 matrix and a vector of length 3'; + ok $A * $v == $prod5, 'Multiply a 3 by 3 matrix and a vector of length 3'; }; subtest 'Construct an elementary matrix' => sub { @@ -357,8 +599,21 @@ subtest 'Construct an elementary matrix' => sub { 'Elementary Matrix with row multiple'; my $E3 = Value::Matrix->E(4, [ 3, 2 ], -3); - is $E3->TeX, Matrix([ [ 1, 0, 0, 0 ], [ 0, 1, 0, 0 ], [ 0, -3, 1, 0 ], [ 0, 0, 0, 1 ] ])->TeX, + is $E3->TeX, Matrix([ [ 1, 0, 0, 0 ], [ 0, 1, -3, 0 ], [ 0, 0, 1, 0 ], [ 0, 0, 0, 1 ] ])->TeX, 'Elementary Matrix with row multiple and add'; + + my $A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ], [ 13, 14, 15, 16 ] ]); + is $A->E([ 1, 4 ])->TeX, + Matrix([ [ 0, 0, 0, 1 ], [ 0, 1, 0, 0 ], [ 0, 0, 1, 0 ], [ 1, 0, 0, 0 ] ])->TeX, + 'Elementary Matrix from syntax $A->E an existing matrix with a row swap'; + + is $A->E([2], 5)->TeX, + Matrix([ [ 1, 0, 0, 0 ], [ 0, 5, 0, 0 ], [ 0, 0, 1, 0 ], [ 0, 0, 0, 1 ] ])->TeX, + 'Elementary Matrix from syntax $A->E an existing matrix with a row multiple'; + + is $A->E([ 2, 4 ], -4)->TeX, + Matrix([ [ 1, 0, 0, 0 ], [ 0, 1, 0, 0 ], [ 0, 0, 1, 0 ], [ 0, -4, 0, 1 ] ])->TeX, + 'Elementary Matrix from syntax $A->E an existing matrix with a row multiple and add'; }; subtest 'Extract a slice from a Matrix' => sub { diff --git a/t/pg_problems/problem_file.t b/t/pg_problems/problem_file.t index a9c6b4605d..47a70e34ff 100644 --- a/t/pg_problems/problem_file.t +++ b/t/pg_problems/problem_file.t @@ -21,8 +21,8 @@ is($pg->{post_header_text}, '', 'post_header_text is empty'); is( $pg->{body_text}, qq{
    \n} - . qq{Enter a value for .\n} - . qq{
    \n} + . qq{Enter a value for .} + . qq{
    } . qq{
    } . qq{} diff --git a/t/pg_problems/source.t b/t/pg_problems/source.t index 3ea7748e0a..dd0c70351b 100644 --- a/t/pg_problems/source.t +++ b/t/pg_problems/source.t @@ -7,7 +7,7 @@ use Test2::V0; use WeBWorK::PG; -my $source = << 'END_SOURCE'; +my $source = <<'END_SOURCE'; DOCUMENT(); loadMacros('PGstandard.pl', 'MathObjects.pl', 'PGML.pl', 'PGcourse.pl'); diff --git a/t/rserve/rexp-unknown.t b/t/rserve/rexp-unknown.t index 2ad6f98266..fab6e58e8d 100644 --- a/t/rserve/rexp-unknown.t +++ b/t/rserve/rexp-unknown.t @@ -11,7 +11,7 @@ use lib "$ENV{PG_ROOT}/lib"; use Rserve::REXP::Unknown; ok my $unk = Rserve::REXP::Unknown->new(sexptype => 42), 'new unknown'; -isa_ok $unk, [ 'Rserve::REXP::Unknown', 'Rserve::REXP' ], 'unkown has correct class classes'; +isa_ok $unk, [ 'Rserve::REXP::Unknown', 'Rserve::REXP' ], 'unknown has correct class classes'; is($unk, $unk, 'self equality'); diff --git a/t/tikz_test/tikz_image.t b/t/tikz_test/tikz_image.t index e26418ef9e..80cfa75c8c 100644 --- a/t/tikz_test/tikz_image.t +++ b/t/tikz_test/tikz_image.t @@ -12,7 +12,7 @@ use LaTeXImage; loadMacros('PGtikz.pl'); my $drawing = createTikZImage(); -$drawing->tex(<< 'END_TIKZ'); +$drawing->tex(<<'END_TIKZ'); \draw (-4,0) -- (4,0); \draw (0,-2) -- (0,2); \draw (0,0) circle[radius=1.5]; diff --git a/t/units/basic_parser.t b/t/units/basic_parser.t index 9b96e63617..749f79f2cb 100644 --- a/t/units/basic_parser.t +++ b/t/units/basic_parser.t @@ -192,7 +192,7 @@ subtest 'Check possible answers compared to the formula 0.009 x^2 T' => sub { is check_score($correct, '0 x^2 T*m/angstrom'), 0, '0 x^2 T*m/angstrom is incorrect'; }; -subtest 'Check possible answers compared to the the number 1 amu' => sub { +subtest 'Check possible answers compared to the number 1 amu' => sub { my $correct = NumberWithUnits('1 amu'); is check_score($correct, '1 amu'), 1, '1 amu is correct'; diff --git a/t/units/electron_volts.t b/t/units/electron_volts.t index 6b5b7c4c3a..36ae87c8b1 100644 --- a/t/units/electron_volts.t +++ b/t/units/electron_volts.t @@ -26,7 +26,7 @@ is \%tev, by_factor(10**12, \%electron_volt), 'tera is factor 10^1 done_testing(); # this sub is useful when reusing units for testing -# NumberWithUnits is mutable and test order dependant +# NumberWithUnits is mutable and test order dependent sub by_factor { my ($value, $unit) = @_; my $new_unit = {%$unit}; # shallow copy hash values diff --git a/tutorial/sample-problems/Algebra/AlgebraicFractionAnswer.pg b/tutorial/sample-problems/Algebra/AlgebraicFractionAnswer.pg index 156d7dfba6..ccba8797e6 100644 --- a/tutorial/sample-problems/Algebra/AlgebraicFractionAnswer.pg +++ b/tutorial/sample-problems/Algebra/AlgebraicFractionAnswer.pg @@ -102,7 +102,7 @@ $multians = MultiAnswer($num, $den)->with( #: The fraction answer is created using a `LayoutTable` from #: PODLINK('niceTables.pl') via its `PGML` syntax. A `LayoutTable` is started #: with `[#` and is ended with `#]*`. Options for the table are set in braces -#: after the ending `#]*`. Cells of the table are started wtih `[.` and ended +#: after the ending `#]*`. Cells of the table are started with `[.` and ended #: with `.]`. Options for a cell (some of which apply to the row as a whole) #: are set in braces after the cell's ending `.]`. Rows of the table are ended #: by a starred cell. For example `[. ... .]*`. Note that the second cell of diff --git a/tutorial/sample-problems/Algebra/GraphToolNumberLine.pg b/tutorial/sample-problems/Algebra/GraphToolNumberLine.pg index 7b4d5fac23..d9b3d2177e 100644 --- a/tutorial/sample-problems/Algebra/GraphToolNumberLine.pg +++ b/tutorial/sample-problems/Algebra/GraphToolNumberLine.pg @@ -24,7 +24,7 @@ DOCUMENT(); loadMacros('PGstandard.pl', 'PGML.pl', 'parserGraphTool.pl', 'PGcourse.pl'); #:% section = setup -#: Two intervals are created with random enpoints. The first +#: Two intervals are created with random endpoints. The first #: one is a bounded interval, and the second an unbounded interval. #: #: The `GraphTool` method creates the graph tool object. The only argument is diff --git a/tutorial/sample-problems/Arithmetic/UnitConversion.pg b/tutorial/sample-problems/Arithmetic/UnitConversion.pg index 1d598376e1..e2f319d313 100644 --- a/tutorial/sample-problems/Arithmetic/UnitConversion.pg +++ b/tutorial/sample-problems/Arithmetic/UnitConversion.pg @@ -34,7 +34,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'contextUnits.pl', 'PGcourse.pl'); #: requested unit. #: #: The second part below is a different conversion question, so the line -#: `Context('Units')->withUnitsFor('length', 'time');` resets the contxt with +#: `Context('Units')->withUnitsFor('length', 'time');` resets the context with #: a new set of units. Context('Units')->withUnitsFor('volume'); diff --git a/tutorial/sample-problems/DiffCalc/AnswerWithUnits.pg b/tutorial/sample-problems/DiffCalc/AnswerWithUnits.pg index 06bb6a197f..5e86170c5b 100644 --- a/tutorial/sample-problems/DiffCalc/AnswerWithUnits.pg +++ b/tutorial/sample-problems/DiffCalc/AnswerWithUnits.pg @@ -30,7 +30,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'contextUnits.pl', 'PGcourse.pl'); #: #: Then, assign the unit "seconds" to the variable `t`. #: -#: Use the differentiation operator `->D('t')` to compute the derivated, and the +#: Use the differentiation operator `->D('t')` to compute the derivative, and the #: evaluation method `->eval()` to evaluate it as a function. All of these will #: now have the expect units. Context('Units')->withUnitsFor('length', 'time')->variables->are(t => 'Real'); diff --git a/tutorial/sample-problems/DiffEq/HeavisideStep.pg b/tutorial/sample-problems/DiffEq/HeavisideStep.pg index baa13bf846..fe69f522ea 100644 --- a/tutorial/sample-problems/DiffEq/HeavisideStep.pg +++ b/tutorial/sample-problems/DiffEq/HeavisideStep.pg @@ -30,7 +30,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'parserFunction.pl', 'PGcourse.pl'); #: #: For more details on adding the Heaviside function to the context, see the #: forum discussion on the -#: [Heaviside step function](https://webwork.maa.org/moodle/mod/forum/discuss.php?d=458). +#: [Heaviside step function](https://wiki.openwebwork.org/moodle/mod/forum/discuss.php?d=458). #: #: For the second question, since answers are checked numerically by comparing #: the student answer to the correct answer at several random points in the diff --git a/tutorial/sample-problems/Geometry/LineSegmentGraphTool.pg b/tutorial/sample-problems/Geometry/LineSegmentGraphTool.pg index d99cd17659..97670090ed 100644 --- a/tutorial/sample-problems/Geometry/LineSegmentGraphTool.pg +++ b/tutorial/sample-problems/Geometry/LineSegmentGraphTool.pg @@ -29,7 +29,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'parserGraphTool.pl', 'PGcourse.pl'); #: `($sx1, $sy1)` and `($sx2, $sy2)`. For a correct answer, the line segment #: must have the given the endpoints and be solid. #: -#: Mulitple line segments can be added to the graph tool, however, if a +#: Multiple line segments can be added to the graph tool, however, if a #: triangle or quadrilateral is desired, see PROBLINK('TriangleGraphTool.pg') #: and PROBLINK('QuadrilateralGraphTool.pg') for more appropriate tools. $sx1 = random(-8, 8); diff --git a/tutorial/sample-problems/IntegralCalc/GraphShadingPlot.pg b/tutorial/sample-problems/IntegralCalc/GraphShadingPlot.pg index 84a691e5f6..b0d42277c6 100644 --- a/tutorial/sample-problems/IntegralCalc/GraphShadingPlot.pg +++ b/tutorial/sample-problems/IntegralCalc/GraphShadingPlot.pg @@ -28,7 +28,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'plots.pl', 'PGcourse.pl'); #: #: First create a `Plot` object with a plotting window that will capture the #: transformed function. The function is then added to the plot with fill -#: characteristics. Note: it is important that the `name` atrribute is defined. +#: characteristics. Note: it is important that the `name` attribute is defined. $a = random(-4, 2); $b = random( 1, 4); diff --git a/tutorial/sample-problems/IntegralCalc/RiemannSumPlot.pg b/tutorial/sample-problems/IntegralCalc/RiemannSumPlot.pg index aa8fe35f47..5e4b813333 100644 --- a/tutorial/sample-problems/IntegralCalc/RiemannSumPlot.pg +++ b/tutorial/sample-problems/IntegralCalc/RiemannSumPlot.pg @@ -25,7 +25,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'plots.pl', 'PGcourse.pl'); #:% section = setup #: For this problem, we make a Riemann Sum plot by plotting the function $y=x^3+3$ #: and the midpoint rectangles. Each of these objects is created separately and added -#: to the the plot. +#: to the plot. #: #: Create a Plot object with the given plotting window and other axis properties. The #: function is made in this case as a MathObject. With `$plot->add_function`, the function diff --git a/tutorial/sample-problems/Misc/RandomPerson.pg b/tutorial/sample-problems/Misc/RandomPerson.pg index 4b5d18fff5..f20e04da63 100644 --- a/tutorial/sample-problems/Misc/RandomPerson.pg +++ b/tutorial/sample-problems/Misc/RandomPerson.pg @@ -48,7 +48,7 @@ $c = random(4, 8, 2); #: capitalized pronouns. #: #: Alternatively, a more-natural set of methods, `they, them, their` and `theirs` -#: will generate the correct subject, object, possession, possesive forms of the +#: will generate the correct subject, object, possession, possessive forms of the #: pronoun. #: #: In addition, there is a `verb` method to conjugate most diff --git a/tutorial/sample-problems/Parametric/SpaceCurveGraph.pg b/tutorial/sample-problems/Parametric/SpaceCurveGraph.pg index c302547295..2b669f9d0f 100644 --- a/tutorial/sample-problems/Parametric/SpaceCurveGraph.pg +++ b/tutorial/sample-problems/Parametric/SpaceCurveGraph.pg @@ -24,7 +24,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'plotly3D.pl', 'PGcourse.pl'); #:% section = setup #: A `plotly3D` graph is created with the `Graph3D` function. There are many -#: options as decribed in PODLINK('plotly3D macro documentation','plotly3D.pl'). +#: options as described in PODLINK('plotly3D macro documentation','plotly3D.pl'). #: In this example, only the `height`, `width`, and `title` are provided. #: #: A parametric curve is added to the graph with the `addCurve` method, which diff --git a/tutorial/sample-problems/Parametric/SurfaceGraph.pg b/tutorial/sample-problems/Parametric/SurfaceGraph.pg index ee29e38b38..6da4bec911 100644 --- a/tutorial/sample-problems/Parametric/SurfaceGraph.pg +++ b/tutorial/sample-problems/Parametric/SurfaceGraph.pg @@ -24,7 +24,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'plotly3D.pl', 'PGcourse.pl'); #:% section = setup #: A `plotly3D` graph is created with the `Graph3D` function. There are many -#: options as decribed in PODLINK('plotly3D macro documentation','plotly3D.pl'). +#: options as described in PODLINK('plotly3D macro documentation','plotly3D.pl'). #: In this example, only the `height`, `width`, and `title` are provided. #: #: A parametric surface is added to the graph with the `addSurface` method, diff --git a/tutorial/sample-problems/ProblemTechniques/AdaptiveParameters.pg b/tutorial/sample-problems/ProblemTechniques/AdaptiveParameters.pg index 8abcd8f3d0..65f7918e0e 100644 --- a/tutorial/sample-problems/ProblemTechniques/AdaptiveParameters.pg +++ b/tutorial/sample-problems/ProblemTechniques/AdaptiveParameters.pg @@ -43,7 +43,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); #: If the answers may be a pure multiple of the correct answer without an #: additive constant, then `0` should not be considered correct. For example, #: if the general solution were `ce^x`. So the commented out line -#: `return 0 if $student == Formula(0);` would needed in that case. +#: `return 0 if Formula(0) == $student;` would needed in that case. #: #: Observe that this checker bypasses the adaptive parameter code for the #: 'produce_equivalence_message' filter. This filter compares the current @@ -54,8 +54,8 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); $aSoln = Compute('e^x - 1')->cmp( checker => sub { my ($correct, $student, $ans) = @_; - # return 0 if $student == Formula(0); # See comments. - if ($self->{_filter_name} ne 'produce_equivalence_message') { + # return 0 if Formula(0) == $student; # See comments. + if ($ans->{_filter_name} ne 'produce_equivalence_message') { my $context = Context()->copy; $context->flags->set(no_parameters => 0); $context->variables->add(C0 => 'Parameter'); diff --git a/tutorial/sample-problems/ProblemTechniques/AskSage.pg b/tutorial/sample-problems/ProblemTechniques/AskSage.pg new file mode 100644 index 0000000000..43c2f6bf52 --- /dev/null +++ b/tutorial/sample-problems/ProblemTechniques/AskSage.pg @@ -0,0 +1,87 @@ +## DESCRIPTION +## Make a call to a Sage cell server from within a problem. +## ENDDESCRIPTION + +## DBsubject(WeBWorK) +## DBchapter(WeBWorK tutorial) +## DBsection(Problem Techniques) +## Date(06/18/2026) +## Institution(Missouri Western State University) +## Author(Glenn Rice) +## MO(1) +## KEYWORDS('matrices', 'AskSage') + +#:% name = AskSage +#:% type = technique +#:% categories = [matrices] + +#:% section = preamble +DOCUMENT(); +loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); + +#:% section = setup +#: Determine a random number of rows, columns, and rank for the random matrix +#: that will be generated by Sage. +#: +#: Send the code to generate the random matrix and its reduced row echelon form +#: to the Sage cell service by calling the `AskSage` method. The first argument +#: to the `AskSage` method must be the code for Sage to evaluate. The results +#: are saved in keys of the `WEBWORK` dictionary. This is a special variable +#: that the `AskSage` method looks for in the returned result and JSON decodes. +#: Use this to save the data that is needed for the problem. The second optional +#: argument is a reference to a hash of options. Generally, the only option is +#: the `seed`. This can be used to pass the problem seed to Sage so that any +#: random calls it makes also use that seed. +#: +#: After the `AskSage` method is returned check to see if the call failed with +#: the `sageReturnedFail` method. If that is true, then the Sage cell server +#: query failed. In that case set fallback values for the variables needed in +#: the problem so that a workable problem is rendered in this case. If the call +#: succeeded the Sage `WEBWORK` dictionary will be decoded into the `webwork` +#: hash key of the reply. +#: +#: Note that by default `AskSage` queries the public Sage cell service at +#: https://sagecell.sagemath.org/service. However, that site no longer accepts +#: public service requests. The service endpoint has been shut down due to +#: security concerns. Thus the `AskSage` method will always fail with that URL. +#: To fix this the WeBWorK administrator of your server can set up a local Sage +#: cell service and configure `AskSage` to use that. +Context('Matrix'); + +my $rows = random(3, 4); +my $columns = random(4, 5); +my $rank = random(2, 3); + +$sageReply = AskSage(<<"END_CODE", { seed => $problemSeed }); +WEBWORK['M'] = random_matrix( + QQ, $rows, $columns, algorithm = 'echelonizable', rank = $rank +).rows() +WEBWORK['RREF'] = matrix(QQ, WEBWORK['M']).rref().rows() +END_CODE + +if (sageReturnedFail($sageReply)) { + # Fallback values for $M and $RREF in case the AskSage call fails. + $M = Matrix([ [ -4, 8, -12, 15 ], [ 5, -10, 15, -19 ], [ 2, -4, 6, -5 ] ]); + $RREF = Matrix([ [ 1, -2, 3, 0 ], [ 0, 0, 0, 1 ], [ 0, 0, 0, 0 ] ]); +} else { + $M = Matrix($sageReply->{webwork}{M}); + $RREF = Matrix($sageReply->{webwork}{RREF}); +} + +#:% section = statement +BEGIN_PGML +Give the reduced row echelon form of the following matrix. + +[```[$M]```] + +The reduced row echelon form is + +[_]*{$RREF} +END_PGML + +#:% section = solution +BEGIN_PGML_SOLUTION +Solution explanation goes here. +END_PGML_SOLUTION + +ENDDOCUMENT(); diff --git a/tutorial/sample-problems/ProblemTechniques/DigitsTolType.pg b/tutorial/sample-problems/ProblemTechniques/DigitsTolType.pg index 4ee1bf2542..343d5c7756 100644 --- a/tutorial/sample-problems/ProblemTechniques/DigitsTolType.pg +++ b/tutorial/sample-problems/ProblemTechniques/DigitsTolType.pg @@ -23,7 +23,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); #:% section = setup #: Several context flags are set for this problem. The meaning of these flags -#: are decribed in the following list. +#: are described in the following list. #: #: * The flag `tolType => 'digits'` switches from the default `'relative'` #: tolerance type to the `'digits'` tolerance type. diff --git a/tutorial/sample-problems/ProblemTechniques/DisableFunctions.pg b/tutorial/sample-problems/ProblemTechniques/DisableFunctions.pg index 483d402212..54ef7f4be0 100644 --- a/tutorial/sample-problems/ProblemTechniques/DisableFunctions.pg +++ b/tutorial/sample-problems/ProblemTechniques/DisableFunctions.pg @@ -59,7 +59,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); #: `sgn` #: * `Vector`: disables `norm`, `unit` #: * `Complex`: disables `arg`, `mod`, `Re`, `Im`, `conj` -#: * `All`: diables all predefined functions +#: * `All`: disables all predefined functions #: #: Alternatively, the following syntax can be used. #: diff --git a/tutorial/sample-problems/ProblemTechniques/Images.pg b/tutorial/sample-problems/ProblemTechniques/Images.pg index 7f2ae9344f..b8675fad1a 100644 --- a/tutorial/sample-problems/ProblemTechniques/Images.pg +++ b/tutorial/sample-problems/ProblemTechniques/Images.pg @@ -78,7 +78,7 @@ $gt = GraphTool('{circle, solid, (1, 1), (2, 2)}'); #: graphs. It is intended to be used to show an interactive graphing tool in #: which student's are expected to graph requested objects, in this case the #: circle that has center `(1, 1)` and passes through the point `(2, 2)`. -#: However, static images can be diplayed as is shown in this example. The +#: However, static images can be displayed as is shown in this example. The #: intent for this usage is to show the correct graph in a solution. BEGIN_PGML * A static image: [!Graph of an exponential!]{'image.png'}{120} diff --git a/tutorial/sample-problems/ProblemTechniques/NamedAnswerRules.pg b/tutorial/sample-problems/ProblemTechniques/NamedAnswerRules.pg new file mode 100644 index 0000000000..16b56f8dfb --- /dev/null +++ b/tutorial/sample-problems/ProblemTechniques/NamedAnswerRules.pg @@ -0,0 +1,149 @@ +## DESCRIPTION +## Add buttons to the MathQuill toolbar for a particular +## answer using a named answer rule. +## ENDDESCRIPTION + +## DBsubject(WeBWorK) +## DBchapter(WeBWorK tutorial) +## DBsection(Problem Techniques) +## Date(06/17/2026) +## Institution(Missouri Western State University) +## Author(Glenn Rice) +## MO(1) +## KEYWORDS('inequalities') + +#:% name = Named answer rules +#:% type = technique +#:% categories = [equations, interval] + +#:% section = preamble +#: Load the PODLINK('parserLinearInequality.pl') macro so that a linear +#: inequality answer can be used. +DOCUMENT(); +loadMacros( + 'PGstandard.pl', 'PGML.pl', + 'parserLinearInequality.pl', 'PGcourse.pl' +); + +#:% section = setup +#: Generate random variables `$a`, `$b`, and `$c` to use for coefficients in the +#: linear inequality to be solved. +#: +#: Then define a linear inequality answer in the `LinearInequality` context, +#: and an interval answer in the `Interval` context. +$a = random(2, 9); +$b = random(2, 9); +$c = random(2, 9); + +Context('LinearInequality'); +$inequalityAnswer = LinearInequality("x <= $a"); + +Context('Interval'); +$intervalAnswer = Interval("(-inf, $a]"); + +#:% section = statement +#: Ask the student to solve the inequality, and give the answer as both a linear +#: inequality and an interval. +#: +#: A named answer is needed for the linear inequality answer in order to add +#: toolbar buttons to the MathQuill toolbar for that answer. Do this with the +#: third option for a PGML answer by calling the `NEW_ANS_NAME` function and +#: assigning that to the variable `$inequalityAnswerName`. Never use hard coded +#: made up answer names in problems. Always obtain an answer name using the +#: `NEW_ANS_NAME` method. Problems that use hard coded made up answer names will +#: not work correctly in many situations. The interval answer does not need to +#: be named. +#: +#: Finally inject a `script` tag to the end of the problem text. It is important +#: that this JavaScript code be executed after the PG MathQuill JavaScript so +#: that the `window.answerQuills` variable will be defined. One way to +#: accomplish this is by executing the code in the `DOMContentLoaded` event. +#: +#: Add the buttons to the toolbar for the linear inequality answer by calling +#: the `addToolbarButtons` function of the `options` object for the `mathField` +#: associated to the `$inequalityAnswerName` answer quill. The first argument +#: for the `addToolbarButtons` function can be either a single toolbar button +#: definition or an array of toolbar button definitions (see below for a +#: description of a toolbar button definition). The second argument can be +#: either a number which is the zero based position at which the new toolbar +#: buttons will be inserted, or the `id` of an existing toolbar button after +#: which the new toolbar buttons will be inserted (note the `id`s of the default +#: toolbar buttons in order are `frac`, `abs`, `sqrt`, `nthroot`, `exponent`, +#: `infty`, `pi`, `vert`, `cup`, and `text`). Note that the second argument can +#: be a negative number which counts backward from the end. In fact the second +#: argument can be entirely omitted. In that case a position of -1 is used which +#: will insert the new button before the last toolbar button (initially the text +#: button). +#: +#: A toolbar button is defined as a JavaScript object with the four properties +#: `id`, `latex`, `tooltip`, and `icon`. +#: +#: - The `id` can be any string, but should not have the same `id` of another +#: toolbar button. +#: - The `latex` for a button is the LaTeX code that will be inserted into the +#: MathQuill input when the toolbar button is pressed. Make sure that +#: backslashes are escaped with another backslash. +#: - The `tooltip` is text that will be shown in a tooltip when the button on +#: the toolbar has keyboard focus or is hovered over by the mouse cursor. Note +#: that the toolbar text should include a brief textual description of what +#: will be inserted when the button is pressed, and in parentheses after that +#: the keyboard sequence that can be typed into the MathQuill input to insert +#: the same thing that pressing the button will insert. +#: - The `icon` is the LaTeX code for icon that will be shown in a static +#: MathQuill field on the toolbar button. Make sure that backslashes are +#: escaped with another backslash. +#: +#: A button can also be removed from the toolbar for an answer using the +#: `removeToolbarButtons` function. The only argument for this function is a +#: string `id` or array of string `id`s for existing toolbar buttons to be +#: removed. For example, +#: `answerQuills.$inequalityAnswerName.mathField.options.removeToolbarButtons('cup');` +#: would remove the union button from the toolbar, and +#: `answerQuills.$inequalityAnswerName.mathField.options.removeToolbarButtons([ 'pi', 'cup' ]);` +#: would remove the pi and union buttons from the toolbar. It is important to +#: note that removing a toolbar button does not prevent a student from entering +#: what the toolbar button would insert. It merely removes the toolbar button. +#: For example, if the `cup` button is removed, a student could still type +#: "union" to enter a union symbol. +BEGIN_PGML +Solve the following inequality. + +>>[``[$b]x + [$c] \leq [$a * $b + $c]``]<< + +Express the answer as an inequality: +[_]{$inequalityAnswer}{15}{ $inequalityAnswerName = NEW_ANS_NAME() }. + +Express the answer in interval notation: [_]{$intervalAnswer}{15} +END_PGML + +TEXT(MODES(TeX => '', HTML => <<"END_SCRIPT")); + +END_SCRIPT + +#:% section = solution +BEGIN_PGML_SOLUTION +Solution explanation goes here. +END_PGML_SOLUTION + +ENDDOCUMENT(); diff --git a/tutorial/sample-problems/README.md b/tutorial/sample-problems/README.md index 64deacc459..3d2cbd2ed7 100644 --- a/tutorial/sample-problems/README.md +++ b/tutorial/sample-problems/README.md @@ -40,7 +40,7 @@ Here's an example: #:% categories = [graph] ``` -## Structure of the code documenation +## Structure of the code documentation The comments that are associated with each file in on the WeBWorK wiki have been embedded as formatted @@ -73,16 +73,16 @@ All lines following the documentation lines are considered code until the next ` ## Generate the documentation -The documentation is generated with the `parse-prob-doc.pl` script in the `bin` +The documentation is generated with the `parse-problem-doc.pl` script in the `bin` directory of pg. There are the following options (and many are required): -- `problem_dir` or `d`: The directory where the sample problems are. This defaults to +- `problem-dir` or `d`: The directory where the sample problems are. This defaults to `PG_ROOT/tutorial/sample-problems` if not passed in. -- `out_dir` or `o`: The directory where the resulting documentation files (HTML) +- `out-dir` or `o`: The directory where the resulting documentation files (HTML) will be located. -- `pod_root` or `p`: The URL where the POD is located. This is needed to +- `pod-base-url` or `p`: The URL where the POD is located. This is needed to correctly link POD from the sample problems. -- `pg_doc_home` or `h`: The URL of the directory for `out_dir`. This is needed +- `sample-problem-base-url` or `s`: The URL of the directory for `out-dir`. This is needed for correct linking. - `verbose` or `v`: verbose mode. @@ -94,7 +94,7 @@ produce four different ways of categorizing the problems. - an html file with the documented PG file - a pg file with the documentation removed. There is a link to this in the html file. -The script `parse-prob-doc.pl` parses each pg file and uses the `problem-template.mt` +The script `parse-problem-doc.pl` parses each pg file and uses the `problem-template.mt` template file to generate the html. This template is processed using the `Mojo::Template` Perl module. See the [Mojo::Template documentation](https://docs.mojolicious.org/Mojo/Template) for more information. diff --git a/tutorial/sample-problems/Statistics/BarGraphStatPlot.pg b/tutorial/sample-problems/Statistics/BarGraphStatPlot.pg new file mode 100644 index 0000000000..63a926b3c0 --- /dev/null +++ b/tutorial/sample-problems/Statistics/BarGraphStatPlot.pg @@ -0,0 +1,105 @@ +## DESCRIPTION +## Find the mean and standard deviation of a list of numbers. +## ENDDESCRIPTION +## DBsubject(WeBWorK) +## DBchapter(WeBWorK tutorial) +## DBsection(WeBWorK tutorial) +## Institution(Fitchburg State University) +## Author(Peter Staab) +## Date(06/25/2026) +## KEYWORDS('statistics', 'bar graph') + +#:% name = Bar Graph (Stat Plot) +#:% subject = [statistics] +#:% type = sample +#:% categories = [graph, statistics] + +#:% section = preamble +#: This is a updated version of PROBLINK('BarGraph.pg') to use the +#: PODLINK('StatisticalPlots.pl') macro, which provides some convenience methods +#: for creating Statistical plots. This also uses the +#: PODLINK('contextPercent.pl') to provide the Percent context. +DOCUMENT(); + +loadMacros( + 'PGstandard.pl', 'PGML.pl', + 'StatisticalPlots.pl', 'contextPercent.pl', + 'PGcourse.pl' +); + +#:% section = setup +#: The 'Percent' context allow students to enter percents. +#: +#: To create a graph, start with a `StatPlot`. The `min` and `max` options in +#: the two directions give the bounding box of the plot. The `tick_delta` for +#: each direction gives the distance between tick marks and the `minor` option is +#: the number of minor ticks between each tick. +#: +#: Use `xtick_labels` to customize the tick labels. The hash reference value +#: is a mapping from tick positions to new labels. +#: +#: The bars are added with the `add_barplot` method passing the x and y data +#: values. The `bar_width` gives the relative width of the bar. If this option +#: is 1, then there is no gap between bars. See PODLINK('StatisticalPlots.pl') +#: for other options to this method. +#: +#: Make sure that an alternate text is added to all graphs for accessibility. Be +#: detailed. + +Context('Percent'); + +@grades = map { random(1, 5) } (0 .. 3); + +$statPlot = StatPlot( + xmin => 0, + xmax => 5, + xtick_delta => 1, + xminor => 0, + ymin => 0, + ymax => 6, + yminor => 0, + ytick_delta => 1, + xtick_labels => { 1 => 'A', 2 => 'B', 3 => 'C', 4 => 'D' }, + show_grid => 0, + rounded_corners => 1 +); + +$statPlot->add_barplot( + [ 1 .. 4 ], ~~@grades, + fill_colors => [ 'Red,', 'Salmon', 'LightYellow', 'LightGreen' ], + stroke_width => 1, + bar_width => 0.9 +); + +$alt_text = + "A bar graph with vertical bars. The height of the bar labelled A " + . "is $grades[0]. The height of the bar labelled B is $grades[1]. " + . " The height of the bar labelled C is $grades[2] and finally the " + . "bar labelled D is $grades[3]."; + +# This code adds up all the numbers in the @grades array. +$num_students = 0; +$num_students += $_ for @grades; + +$perA = Compute($grades[0] / $num_students); + +#:% section = statement +#: In this case, we plot the bar graph and ask a question. +#: Since the context is `Percent`, the students can +#: answer the question with either a fraction, decimal or percent. +#: If only percent answers is desired, see the flags for the Percent context +#: in the macro. +BEGIN_PGML +The following is a distribution of grades on a Statistics quiz. +>>[! [$alt_text] !]{$statPlot}{400}<< + +What percentage of students earned an A? [_]{$perA} +END_PGML + +#:% section = solution +BEGIN_PGML_SOLUTION +The total number of students that took the quiz is [$total] so the percentage is +the number of A students or [`[$grades[0]]/[$num_students] = [$perA] `] +END_PGML_SOLUTION + +ENDDOCUMENT(); diff --git a/tutorial/sample-problems/Statistics/BoxPlot.pg b/tutorial/sample-problems/Statistics/BoxPlot.pg new file mode 100644 index 0000000000..0b8460b003 --- /dev/null +++ b/tutorial/sample-problems/Statistics/BoxPlot.pg @@ -0,0 +1,132 @@ +## DESCRIPTION +## Produce a box plot from both a set of numbers and from the quartiles. +## ENDDESCRIPTION +## DBsubject(WeBWorK) +## DBchapter(WeBWorK tutorial) +## DBsection(WeBWorK tutorial) +## Institution(Fitchburg State University) +## Author(Peter Staab) +## Date(06/25/2026) +## KEYWORDS('statistics', 'box plot') + +#:% name = Boxplot +#:% subject = [statistics] +#:% type = sample +#:% categories = [graph, statistics] + +#:% section = preamble +#: Use the PODLINK('StatisticalPlots.pl') macro, which provides some convenience methods +#: for creating Statistical plots. This also uses the +#: PODLINK('PGstatisticsmacros.pl') for access to random normally-distributed numbers. +DOCUMENT(); + +loadMacros( + 'PGstandard.pl', 'PGML.pl', + 'StatisticalPlots.pl', 'PGstatisticsmacros.pl', + 'PGcourse.pl' +); + +#:% section = setup +#: +#: The `urand` function is used for normally distributed random numbers. This example +#: generates an array of length 75 with mean 100 and standard deviation 25. +# +#: To create a graph, start with a `StatPlot`. The `min` and `max` options in +#: the two directions give the bounding box of the plot. The `tick_delta` for +#: each direction gives the distance between tick marks and the `minor` option is +#: the number of minor ticks between each tick. +#: +#: The box plot is created with the `add_boxplot` method in which the data is the +#: first argument (it is an array reference, which is why the `~~` is needed). The +#: remaining arguments are options to the box plot. See PODLINK('StatisticalPlots.pl') +#: for other options to this method. +#: +#: The second box plot shows that the min/max, median and quartiles can be passed in +#: as an alternative to an array reference of numbers. + +#: Make sure that an alternate text is added to all graphs for accessibility. Be +#: detailed. + +@data = urand(100, 25, 75, 6); + +$boxplotA = StatPlot( + xmin => 0, + xmax => 200, + xtick_delta => 25, + show_grid => 0, + ymin => -5, + ymax => 25, + yvisible => 0, + aspect_ratio => 4, + rounded_corners => 1 +); + +$boxplotA->add_boxplot(~~@data, fill_color => 'lightblue', stroke_width => 1); + +# the five point summary is needed for the alt text: + +@five_point = five_point_summary(@data); +$minA = Round($five_point[0]); +$q1A = Round($five_point[1]); +$medA = Round($five_point[2]); +$q3A = Round($five_point[3]); +$maxA = Round($five_point[4]); + +$alt_textA = + "A horizontal box plot with the box between $q1A and $q3A and vertical " + . "line at $medA. The is line from the box to the left from $q1A to $minA and a " + . "line to the right from $q3A to $maxA."; + +# Alternatively, we can set these value for the plot. + +$boxplotB = StatPlot( + xmin => 100, + xmax => 400, + ymin => -5, + ymax => 25, + xtick_delta => 50, + yvisible => 0, + show_grid => 0, + aspect_ratio => 4, + rounded_corners => 1 +); + +$minB = random(150, 175, 5); +$q1B = random(180, 225, 5); +$medB = random(250, 275, 5); +$q3B = random(280, 320, 10); +$maxB = random(325, 350, 5); + +$boxplotB->add_boxplot({ + min => $minB, + q1 => $q1B, + median => $medB, + q3 => $q3B, + max => $maxB, + outliers => [ 115, 130, 375 ] +}); + +$alt_textB = + "A horizontal box plot with the box between $q1B and $q3B and vertical " + . "line at $medA. The is line from the box to the left from $q1B to $minB and a " + . "line to the right from $q3B to $maxB. There are also 3 small plus signs at " + . "115, 130 and 375."; + +#:% section = statement +#: For this problem, only the plots are shown. Generally the questions would go +#: here. Again, add a detailed alt text for each plot. +BEGIN_PGML + +>>[! [$alt_textA] !]{$boxplotA}{400}<< + +>>[! [$alt_textB] !]{$boxplotB}{400}<< + + +END_PGML + +#:% section = solution +BEGIN_PGML_SOLUTION +Add solution here. +END_PGML_SOLUTION + +ENDDOCUMENT(); diff --git a/tutorial/sample-problems/Statistics/DonutPlot.pg b/tutorial/sample-problems/Statistics/DonutPlot.pg new file mode 100644 index 0000000000..00903ebcb9 --- /dev/null +++ b/tutorial/sample-problems/Statistics/DonutPlot.pg @@ -0,0 +1,83 @@ +## DESCRIPTION +## Produce a donut plot. +## ENDDESCRIPTION +## DBsubject(WeBWorK) +## DBchapter(WeBWorK tutorial) +## DBsection(WeBWorK tutorial) +## Institution(Fitchburg State University) +## Author(Peter Staab) +## Date(06/25/2026) +## KEYWORDS('statistics', 'bar graph') + +#:% name = Donut Plot +#:% subject = [statistics] +#:% type = sample +#:% categories = [graph, statistics] +#:% see_also = [PieChart.pg] + +#:% section = preamble +#: This uses the `add_piechart` method in the PODLINK('StatisticalPlots.pl') +#: macro with the option to make it a donut plot. +#: A pie chart version of this is available at PROBLINK('PieChart.pg'). +DOCUMENT(); + +loadMacros("PGstandard.pl", 'PGML.pl', 'StatisticalPlots.pl', 'PGcourse.pl'); + +#:% section = setup +#: In this example, the size of each wedge in the donut plot is given as an +#: array. +#: +#: The StatPlot is created with the axes and grid turned off. The +#: `add_piechart` method takes the number values in the `@values` array. The +#: values must be passed in as an array reference (which is why the `~~` is needed). +#: +#: For the options, the `labels` also need to be passed in as an array reference. +#: The `radius` and `inner_radius` gives the outer and inner radii of the donut. +#: The `angle_offset` shifts the angle (in degrees) of the starting sector from +#: the horizontal axis. +#: +#: The default colors come from the `rainbow` palette, and other options +#: exist. +#: +#: A detailed alternative text should accompany all plots/graphs. + +@values = (1, 5, 8, 2, 4); +@animals = ('Anteater', 'Bobcat', 'Camel', 'Dingo', 'Elephant'); + +$stat_plot = StatPlot(xvisible => 0, yvisible => 0, show_grid => 0); +$stat_plot->add_piechart( + ~~@values, + labels => ~~@animals, + radius => 3.25, + inner_radius => 2.5, + angle_offset => 30, + stroke_width => 1, + rounded_corners => 1 +); + +$total = 0; +$total += $_ for (@values); +$alt_text = 'A pie chart of animals. '; +for (0 .. 4) { + $alt_text .= + "The size of the $animals[$_] wedge is about " + . Round(100 * $values[$_] / $total, 1) + . '% of the total. '; +} + +#:% section = statement +#: In this case, only the plot is provided with the alt text. +BEGIN_PGML + +>> Donut Plot of the number of animals at the local zoo << + +>>[! [$alt_text] !]{$stat_plot}{400}<< + +END_PGML + +#:% section = solution +BEGIN_PGML_SOLUTION +Place a detailed solution here. +END_PGML_SOLUTION + +ENDDOCUMENT(); diff --git a/tutorial/sample-problems/Statistics/Histogram.pg b/tutorial/sample-problems/Statistics/Histogram.pg new file mode 100644 index 0000000000..07e0457571 --- /dev/null +++ b/tutorial/sample-problems/Statistics/Histogram.pg @@ -0,0 +1,100 @@ +## DESCRIPTION +## Find the mean and standard deviation of a list of numbers. +## ENDDESCRIPTION +## DBsubject(WeBWorK) +## DBchapter(WeBWorK tutorial) +## DBsection(WeBWorK tutorial) +## Institution(Missouri Western) +## Author(Glenn Rice) +## Date(06/25/2026) +## KEYWORDS('statistics', 'histogram') + +#:% name = Histogram +#:% subject = [statistics] +#:% type = sample +#:% categories = [graph, statistics] + +#:% section = preamble +#: This produces a histogram using the PODLINK('StatisticalPlots.pl') macro, which +#: provides some convenience methods for creating Statistical plots. This also uses +#: PODLINK('PGstatisticsmacros.pl') to generate random values from the Normal +#: distribution. +DOCUMENT(); + +loadMacros( + "PGstandard.pl", "PGML.pl", + 'StatisticalPlots.pl', 'PGstatisticsmacros.pl', + "PGcourse.pl" +); + +#:% section = setup +#: +#: The `urand` function produces normally distributed random numbers. This will +#: generate an array of 50 data values with a mean of 30 and standard deviation +# of 9. See PODLINK('PGstatisticsmacros.pl') for more information. +#: +#: To get nice bin sizes (in this case of width 5), the min and max are found and then +#: are passed into the `add_histogram` method. The vertical maximum is calculated from the +#: data and then the ymax is set with the `->axes->yaxis` method. +#: +#: See PODLINK('StatisticalPlots.pl') for other options to this method. +#: +#: Make sure that an alternate text is added to all graphs for accessibility. Be +#: detailed. + +$dx = 5; # bin size +@data = urand(30, 9, 50, 6); +$min = floor(min(@data) / $dx) * $dx; +$max = ceil(max(@data) / $dx) * $dx; + +$histogramVertical = StatPlot( + xmin => $min - $dx / 2, + xmax => $max + $dx / 2, + xtick_distance => $dx, + xminor => 0, + xmajor => 0, + xlabel => '', + xlocation => 'bottom', + ymin => 0, + ytick_distance => 2, + yminor => 1, + ylabel => '', + ylocation => 'left', + rounded_corners => 1 +); +(undef, $vFrequencies) = $histogramVertical->add_histogram( + ~~@data, + min => $min, + max => $max, + bins => ($max - $min) / $dx, + fill_color => 'blue', + fill_opacity => 0.4, + stroke_width => 1 +); +$histogramVertical->axes->yaxis(max => max(@$vFrequencies) + 1); + +# use the $vFrequencies to generate the alternative text. +$alt_text = 'A histogram with ' . scalar(@$vFrequencies) . ' bins. '; +for my $i (1 .. scalar(@$vFrequencies)) { + $alt_text .= + "Bin $i is from " + . ($min + ($i - 1) * $dx) . ' to ' + . ($min + $i * $dx) + . ' and the height is ' + . $vFrequencies->[ $i - 1 ] . '. '; +} + +#:% section = statement +#: In this case, only the histogram is plotted. Make sure to add the alt text. +BEGIN_PGML + +>>[! [$alt_text] !]{$histogramVertical}{400}<< + +END_PGML + +#:% section = solution +BEGIN_PGML_SOLUTION +If a question was asked, provide a solution. +END_PGML_SOLUTION + +ENDDOCUMENT(); diff --git a/tutorial/sample-problems/Statistics/LinearRegression.pg b/tutorial/sample-problems/Statistics/LinearRegression.pg index 7fc05eef6c..7e95e8f14e 100644 --- a/tutorial/sample-problems/Statistics/LinearRegression.pg +++ b/tutorial/sample-problems/Statistics/LinearRegression.pg @@ -11,7 +11,7 @@ #:% name = Linear Regression #:% subject = [statistics] #:% type = sample -#:% categories = [graph, statistics] +#:% categories = [statistics] #:% section = preamble #: The PODLINK('PGstatisticsmacros.pl') macro provides the `sample_correlation` diff --git a/tutorial/sample-problems/Statistics/MultiBoxPlot.pg b/tutorial/sample-problems/Statistics/MultiBoxPlot.pg new file mode 100644 index 0000000000..47bad79ad7 --- /dev/null +++ b/tutorial/sample-problems/Statistics/MultiBoxPlot.pg @@ -0,0 +1,118 @@ +## DESCRIPTION +## Produce multiple box plots. +## ENDDESCRIPTION +## DBsubject(WeBWorK) +## DBchapter(WeBWorK tutorial) +## DBsection(WeBWorK tutorial) +## Institution(Fitchburg State University) +## Author(Peter Staab) +## Date(06/25/2026) +## KEYWORDS('statistics', 'box plot') + +#:% name = Boxplot (Multiple) +#:% subject = [statistics] +#:% type = sample +#:% categories = [graph, statistics] + +#:% section = preamble +#: The PODLINK('StatisticalPlots.pl') macro is included. This provides some +#: convenience methods for creating Statistical plots. +DOCUMENT(); + +loadMacros('PGstandard.pl', 'PGML.pl', 'StatisticalPlots.pl', 'PGcourse.pl'); + +#:% section = setup +#: To create a graph, start with a `StatPlot`. The `min` and `max` options in +#: the two directions give the bounding box of the plot. The `tick_delta` for +#: each direction gives the distance between tick marks and the `minor` option is +#: the number of minor ticks between each tick. +#: +#: The three box plots are created by defining the `$params`, an array reference of the +#: needed parameters. For each set of parameters, the box plot is created +#: with the `add_boxplot`. The remaining arguments are options to the box plot. +#: Note that in this example, we have selected three different colors for the +#: box plots and each is given a whisker cap. See PODLINK('StatisticalPlots.pl') +#: for other options to this method. +#: +#: Make sure that an alternate text is added to all graphs for accessibility. Be +#: detailed. + +$params = [ + { + min => 700, + q1 => 1100, + median => 1300, + q3 => 1700, + max => 2150 + + }, + { + min => 400, + q1 => 850, + median => 1150, + q3 => 1350, + max => 2000 + }, + { + min => 600, + q1 => 950, + median => 1175, + q3 => 1250, + max => 1600 + } +]; + +$statPlot = StatPlot( + ymin => -2, + ymax => 8, + ytick_delta => 2, + yminor => 0, + yvisible => 1, + ytick_labels => { 6 => 'Fri.', 4 => 'Sat.', 2 => 'Sun.' }, + show_grid => 0, + xmin => -500, + xmax => 3000, + xtick_delta => 500 +); +@fill_colors = ('LightBlue', 'LightGreen', 'Magenta'); +@box_centers = (2, 4, 6); +for (0 .. $#box_centers) { + $statPlot->add_boxplot( + $params->[$_], + orientation => 'horizontal', + box_center => $box_centers[$_], + box_width => 1, + fill_color => $fill_colors[$_], + whisker_cap => 1 + ); +} +$alt_text = + 'Three horizontal box plots. ' + . "The topmost one is labeled 'Fri.' with left most whisker $params->[0]{min} " + . "the left side of the box is at $params->[0]{q1} and vertical line at " + . "$params->[0]{median}, right side of the box at $params->[0]{q3} " + . "and rightmost whisker at $params->[0]{max}." + . "The box plot in the middle is labelled 'Sat.' with left most whisker " + . "$params->[1]{min} the left side of the box is at $params->[1]{q1} and " + . "vertical line at $params->[1]{median}, right side of the box at " + . " $params->[1]{q3} and rightmost whisker at $params->[1]{max}." + . "The box plot toward the bottom is labelled 'Sun.' with left most whisker " + . "$params->[2]{min} the left side of the box is at $params->[2]{q1} and " + . "vertical line at $params->[2]{median}, right side of the box at " + . " $params->[2]{q3} and rightmost whisker at $params->[2]{max}."; + +#:% section = statement +#: In this case, only the plot is shown. Generally here is where questions are +#: asked. Also note that the alt test is provided to the plot. +BEGIN_PGML + +>>[! [$alt_text] !]{$statPlot}{400}<< + +END_PGML + +#:% section = solution +BEGIN_PGML_SOLUTION +Place a detailed solution here. +END_PGML_SOLUTION + +ENDDOCUMENT(); diff --git a/tutorial/sample-problems/Statistics/PieChart.pg b/tutorial/sample-problems/Statistics/PieChart.pg new file mode 100644 index 0000000000..7c3e85e322 --- /dev/null +++ b/tutorial/sample-problems/Statistics/PieChart.pg @@ -0,0 +1,79 @@ +## DESCRIPTION +## Produce a pie chart. +## ENDDESCRIPTION +## DBsubject(WeBWorK) +## DBchapter(WeBWorK tutorial) +## DBsection(WeBWorK tutorial) +## Institution(Fitchburg State University) +## Author(Peter Staab) +## Date(06/25/2026) +## KEYWORDS('statistics', 'bar graph') + +#:% name = Pie Chart +#:% subject = [statistics] +#:% type = sample +#:% categories = [graph, statistics] +#:% see_also = [DonutPlot.pg] + +#:% section = preamble +#: This uses the `add_piechart` method in the PODLINK('StatisticalPlots.pl') +#: macro. A donut plot version of this is available at PROBLINK('DonutPlot.pg'). +DOCUMENT(); + +loadMacros("PGstandard.pl", 'PGML.pl', 'StatisticalPlots.pl', 'PGcourse.pl'); + +#:% section = setup +#: In this example, the size of each wedge in the pie chart is given as an +#: array. +#: +#: The StatPlot is created with the axes and grid turned off. The +#: `add_piechart` method requires an array reference of values that will +#: generate pie wedges with given size. Optionally, the labels are included +#: as an array reference and are placed outside the piechart. The `radius` +#: gives the size of the circle and the `angle_offset` shifts the angle (in +#: degrees) of the starting sector from the horizontal axis. +#: +#: The default colors come from the `rainbow` palette, and other options +#: exist. +#: +#: A detailed alternative text should accompany all plots/graphs. + +@values = (1, 5, 8, 2, 4); +@animals = ('Anteater', 'Bobcat', 'Camel', 'Dingo', 'Elephant'); + +$stat_plot = StatPlot(xvisible => 0, yvisible => 0, show_grid => 0); +$stat_plot->add_piechart( + ~~@values, + labels => ~~@animals, + radius => 3.25, + angle_offset => 30, + stroke_width => 1, + rounded_corners => 1 +); + +$total = 0; +$total += $_ for (@values); +$alt_text = 'A pie chart of animals. '; +for (0 .. 4) { + $alt_text .= + "The size of the $animals[$_] wedge is about " + . Round(100 * $values[$_] / $total, 1) + . '% of the total. '; +} + +#:% section = statement +#: In this case, we just plot the box plots. +BEGIN_PGML + +>> Pie Chart of the number of animals at the local zoo << + +>>[! [$alt_text] !]{$stat_plot}{400}<< + +END_PGML + +#:% section = solution +BEGIN_PGML_SOLUTION +Place a detailed solution here. +END_PGML_SOLUTION + +ENDDOCUMENT(); diff --git a/tutorial/sample-problems/Statistics/ScatterPlot.pg b/tutorial/sample-problems/Statistics/ScatterPlot.pg index 9885ed7ffb..bce5bfaaa9 100644 --- a/tutorial/sample-problems/Statistics/ScatterPlot.pg +++ b/tutorial/sample-problems/Statistics/ScatterPlot.pg @@ -15,9 +15,12 @@ #:% type = Sample #:% subject = [statistics, plot] #:% categories = [plots] +#:% see_also = [ScatterPlotStatPlot.pg] #:% section = preamble -#: Make sure that PODLINK('plots.pl') is loaded. +#: This produces a scatter plot with PODLINK('plots.pl'). A version of this +#: with PODLINK('StatisticalPlots.pl') is available in +#: PROBLINK('ScatterPlotStatPlot.pg'). DOCUMENT(); loadMacros( @@ -34,7 +37,7 @@ loadMacros( #: components. #: #: For the plot, first set up the `Plot` with a plotting window that will -#: caputure the data. The `add_dataset` method adds the data to the plot +#: capture the data. The `add_dataset` method adds the data to the plot #: with circles and color blue. #: #: The line is added to the dataset with the `add_function` method. The 3rd @@ -70,9 +73,10 @@ $plot->add_function( ); #:% section = statement -#: The data is formatted by putting the array data in a nice format using both -#: join (which joins an array separated by ', ') and map (which creates a new -#: array that formats as (x1, y1)). The [@ @] is used to run perl. +#: To format the points in the `@data` array in a human-readable manner, these +#: are first put in "(x, y)" form using the `map` function. The resulting array +#: is then concatenated with the `join` command. Since both map and join are +#: perl commands, this compound command is placed within a `[@ @]`. #: #: The `[! !]{}{}` is a PGML format for inserting an image. BEGIN_PGML diff --git a/tutorial/sample-problems/Statistics/ScatterPlotStatPlot.pg b/tutorial/sample-problems/Statistics/ScatterPlotStatPlot.pg new file mode 100644 index 0000000000..a245356500 --- /dev/null +++ b/tutorial/sample-problems/Statistics/ScatterPlotStatPlot.pg @@ -0,0 +1,98 @@ +## DESCRIPTION +## Scatter Plot with best-fit line using StatPlots +## ENDDESCRIPTION + +## DBsubject(WeBWorK) +## DBchapter(WeBWorK tutorial) +## DBsection(PGML tutorial 2015) +## Date(06/01/2026) +## Institution(Boise State University) +## Author(Jaimos Skriletz) +## MO(1) +## KEYWORDS('riemann sum', 'plot') + +#:% name = Scatter Plot (Statistical Plots) +#:% type = Sample +#:% subject = [statistics, plot] +#:% categories = [plots] + +#:% section = preamble +#: Make sure that PODLINK('StatisticalPlots.pl') is loaded. This is a version +#: of the problem in PROBLINK('ScatterPlot.pg') that uses the scatter plot feature +#: in StatisticalPlots.pl. +DOCUMENT(); + +loadMacros( + 'PGstandard.pl', 'PGML.pl', + 'StatisticalPlots.pl', 'PGstatisticsmacros.pl', + 'PGcourse.pl' +); + +#:% section = setup +#: For this problem, we have a data set stored as an array of array +#: references. First we find the best-fit line using the `linear_regression` +#: function of PODLINK('PGstatisticsmacros.pl'). Note that the x and y data +#: values need to be in array references, and the 2nd and 3rd lines extract the +#: components. +#: +#: Set up the `StatPlot` with a plotting window that will +#: contain the data. The `add_dataset` method adds the data to the plot +#: with circle marks that are colored blue. +#: +#: The line is added to the dataset with the `add_function` method. The 3rd +#: and 4th arguments are the xmin and xmax of the domain. + +@data = ([ 5, 40 ], [ 7, 120 ], [ 12, 180 ], [ 16, 210 ], [ 20, 240 ]); + +$x = [ map { $_->[0] } @data ]; +$y = [ map { $_->[1] } @data ]; +($m, $b) = linear_regression($x, $y); + +$plot = StatPlot( + xmin => 0, + xmax => 24, + xtick_delta => 4, + ymin => 0, + ymax => 300, + ytick_delta => 50, + aria_label => 'Linear Regression' +); + +$plot->add_scatterplot( + ~~@data, + marks => 'circle', + color => 'blue' +); +$plot->add_function( + "$m x + $b", + 'x', + 1, + 23, + color => 'black', + width => 1, +); + +#:% section = statement +#: To format the points in the `@data` array in a human-readable manner, these +#: are first put in "(x, y)" form using the `map` function. The resulting array +#: is then concatenated with the `join` command. Since both map and join are +#: perl commands, this compound command is placed within a `[@ @]`. +#: +#: The `[! !]{}{}` is a PGML format for inserting an image. +BEGIN_PGML +A scatter plot of the data: + +[``` [@ join(', ', map { "($_->[0], $_->[1])" } @data) @] ```] + +together with the best-fit line is given by the following plot. + +>> [! Scatter Plot with best fit line !]{$plot}{500} << + +END_PGML + +#:% section = solution +BEGIN_PGML_SOLUTION +Solution explanation goes here. +END_PGML_SOLUTION + +ENDDOCUMENT(); diff --git a/tutorial/sample-problems/VectorCalc/VectorLineSegment1.pg b/tutorial/sample-problems/VectorCalc/VectorLineSegment1.pg index 0210fc1ff3..859328293e 100644 --- a/tutorial/sample-problems/VectorCalc/VectorLineSegment1.pg +++ b/tutorial/sample-problems/VectorCalc/VectorLineSegment1.pg @@ -1,5 +1,5 @@ ## DESCRIPTION -## A Vector-value parametric line segment with a general paramterization +## A Vector-value parametric line segment with a general parameterization ## ENDDESCRIPTION ## DBsubject(WeBWorK) @@ -32,7 +32,7 @@ loadMacros( ); #:% section = setup -#: Create a `MutiAnswer` object that is used to evaluate the student's vector +#: Create a `MultiAnswer` object that is used to evaluate the student's vector #: equation at the starting and ending times provided by the student. For #: example, both of the student answers `<4, 0> + t<-4, 2>` for `t` between `0` #: and `1`, and `<4,0> + t<-2,1>` for t between `0` and `2` will be marked diff --git a/tutorial/sample-problems/VectorCalc/Vectors.pg b/tutorial/sample-problems/VectorCalc/Vectors.pg index b1f25d9d4f..498022c208 100644 --- a/tutorial/sample-problems/VectorCalc/Vectors.pg +++ b/tutorial/sample-problems/VectorCalc/Vectors.pg @@ -54,7 +54,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); #: example, to obtain the first component of `$v3` use `$v3->extract(1)`. Note #: that the index of `extract` starts at 1, not 0. #: -#: See the [vector](https://webwork.maa.org/wiki/Vector_(MathObject_Class)) +#: See the [vector](https://wiki.openwebwork.org/wiki/Vector_(MathObject_Class)) #: wiki page for more information about MathObject vectors. Context('Vector'); # Uncomment to display vectors in ijk format. diff --git a/tutorial/templates/general-layout.mt b/tutorial/templates/general-layout.mt index 3f50146abd..b7d9fac40d 100644 --- a/tutorial/templates/general-layout.mt +++ b/tutorial/templates/general-layout.mt @@ -5,7 +5,7 @@ PG Sample Problems - + - + diff --git a/tutorial/templates/index.html b/tutorial/templates/index.html new file mode 100644 index 0000000000..a911f7a41c --- /dev/null +++ b/tutorial/templates/index.html @@ -0,0 +1,139 @@ + + + + PG Documentation + + + + + + + + +
    +
    +

    PG Documentation

    +
    +
    +
    +
    + +
    + + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +

    + This is the documentation for PG, the problem authoring language for WeBWorK. The links below + include sample problems demonstrating problem authoring techniques and POD (Plain Old + Documentation), explaining how to use PG macros and related modules. +

    +
    +
    + +
    + + diff --git a/tutorial/templates/problem-template.mt b/tutorial/templates/problem-template.mt index dfd9d3b798..f31700139c 100644 --- a/tutorial/templates/problem-template.mt +++ b/tutorial/templates/problem-template.mt @@ -5,12 +5,12 @@ <%= $filename %> - - + + + - % # Default explanations @@ -30,7 +30,7 @@

    <%= $description %>

    @@ -44,7 +44,9 @@
  • - + Link to a list of all available functions