├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ ├── check-formats.yml │ └── unit-tests.yml ├── .gitignore ├── .perltidyrc ├── .prettierrc ├── LICENSE ├── README ├── README.md ├── README_maketext ├── VERSION ├── assets └── tex │ ├── CAPA.tex │ ├── PGML.tex │ └── pg.sty ├── bin ├── convert-to-pgml.pl ├── parse-problem-doc.pl ├── perltidy-pg.pl ├── perltidy-pg.rc ├── run-perltidy.pl └── update-localization-files ├── conf └── pg_config.dist.yml ├── cpanfile ├── doc └── MathObjects │ ├── MathObjectsAnswerCheckers.pod │ ├── README.pod │ ├── UsingMathObjects.pod │ ├── extensions │ ├── 1-function.pg │ ├── 2-function.pg │ ├── 3-operator.pg │ ├── 4-list.pg │ ├── 5-operator.pg │ ├── 6-precedence.pg │ └── 7-context.pg │ └── problems │ ├── sample01.pg │ ├── sample02.pg │ ├── sample03.pg │ ├── sample04.pg │ ├── sample05.pg │ ├── sample06.pg │ ├── sample07.pg │ ├── sample08.pg │ ├── sample09.pg │ ├── sample10.pg │ ├── sample11.pg │ ├── sample12.pg │ ├── sample13.pg │ ├── sample14.pg │ ├── sample15.pg │ ├── sample16.pg │ ├── sample17.pg │ ├── sample18.pg │ ├── sample19.pg │ ├── sample20.pg │ ├── sample21.pg │ └── sample22.pg ├── docker ├── README.md └── pg.Dockerfile ├── htdocs ├── generate-assets.js ├── helpFiles │ ├── Entering-Angles.html │ ├── Entering-Decimals.html │ ├── Entering-Equations.html │ ├── Entering-Essays.html │ ├── Entering-Exponents.html │ ├── Entering-Formulas.html │ ├── Entering-Formulas10.html │ ├── Entering-Fractions.html │ ├── Entering-Inequalities.html │ ├── Entering-Intervals.html │ ├── Entering-Limits.html │ ├── Entering-Logarithms.html │ ├── Entering-Logarithms10.html │ ├── Entering-Matrices.html │ ├── Entering-Numbers.html │ ├── Entering-Points.html │ ├── Entering-Syntax.html │ ├── Entering-Units.html │ ├── Entering-Vectors.html │ ├── IntervalNotation.html │ ├── PDE-notation.html │ ├── Syntax.html │ └── Units.html ├── js │ ├── AppletSupport │ │ └── ww_applet_support.js │ ├── Base64 │ │ └── Base64.js │ ├── DragNDrop │ │ ├── dragndrop.js │ │ └── dragndrop.scss │ ├── Essay │ │ └── essay.js │ ├── Feedback │ │ └── feedback.js │ ├── GraphTool │ │ ├── cubictool.js │ │ ├── graphtool.js │ │ ├── graphtool.scss │ │ ├── images │ │ │ ├── CircleTool.svg │ │ │ ├── CubicTool.svg │ │ │ ├── DashTool.svg │ │ │ ├── ExcludePointParenthesisTool.svg │ │ │ ├── ExcludePointTool.svg │ │ │ ├── FillTool.svg │ │ │ ├── HorizontalParabolaTool.svg │ │ │ ├── IncludePointBracketTool.svg │ │ │ ├── IncludePointTool.svg │ │ │ ├── IntervalBracketTool.svg │ │ │ ├── IntervalTool.svg │ │ │ ├── LineTool.svg │ │ │ ├── PointTool.svg │ │ │ ├── QuadraticTool.svg │ │ │ ├── SelectTool.svg │ │ │ ├── SolidTool.svg │ │ │ └── VerticalParabolaTool.svg │ │ ├── intervaltools.js │ │ ├── pointtool.js │ │ └── quadratictool.js │ ├── ImageView │ │ ├── imageview.js │ │ └── imageview.scss │ ├── Knowls │ │ ├── knowl.js │ │ └── knowl.scss │ ├── LiveGraphics │ │ └── liveGraphics.js │ ├── MathQuill │ │ ├── mqeditor.js │ │ └── mqeditor.scss │ ├── MathView │ │ ├── mathview.js │ │ ├── mathview.scss │ │ └── mv_locale_us.js │ ├── Problem │ │ ├── details-accordion.js │ │ └── problem.scss │ ├── QuickMatrixEntry │ │ └── quickmatrixentry.js │ ├── RadioButtons │ │ └── RadioButtons.js │ ├── RadioMultiAnswer │ │ ├── RadioMultiAnswer.js │ │ └── RadioMultiAnswer.scss │ ├── Scaffold │ │ ├── scaffold.js │ │ └── scaffold.scss │ └── UnionTables │ │ └── union-tables.scss ├── package-lock.json └── package.json ├── lib ├── AlgParser.pm ├── AnswerHash.pm ├── AnswerIO.pm ├── Applet.pm ├── ChoiceList.pm ├── Chromatic.pm ├── Circle.pm ├── Complex.pm ├── Complex1.pm ├── Distributions.pm ├── DragNDrop.pm ├── Fraction.pm ├── Fun.pm ├── Hermite.pm ├── LaTeXImage.pm ├── Label.pm ├── LimitedPolynomial.pm ├── List.pm ├── Match.pm ├── Matrix.pm ├── MatrixReal1.pm ├── Multiple.pm ├── PGUtil.pm ├── PGalias.pm ├── PGanswergroup.pm ├── PGcore.pm ├── PGloadfiles.pm ├── PGrandom.pm ├── PGresource.pm ├── PGresponsegroup.pm ├── Parser.pm ├── Parser │ ├── BOP.pm │ ├── BOP │ │ ├── add.pm │ │ ├── comma.pm │ │ ├── cross.pm │ │ ├── divide.pm │ │ ├── dot.pm │ │ ├── equality.pm │ │ ├── multiply.pm │ │ ├── power.pm │ │ ├── subtract.pm │ │ ├── undefined.pm │ │ ├── underscore.pm │ │ └── union.pm │ ├── Complex.pm │ ├── Constant.pm │ ├── Context.pm │ ├── Context │ │ ├── Constants.pm │ │ ├── Default.pm │ │ ├── Functions.pm │ │ ├── Operators.pm │ │ ├── Parens.pm │ │ ├── Reduction.pm │ │ ├── Strings.pm │ │ └── Variables.pm │ ├── Differentiation.pm │ ├── Function.pm │ ├── Function │ │ ├── complex.pm │ │ ├── hyperbolic.pm │ │ ├── numeric.pm │ │ ├── numeric2.pm │ │ ├── trig.pm │ │ ├── undefined.pm │ │ └── vector.pm │ ├── Item.pm │ ├── Legacy.pm │ ├── Legacy │ │ ├── LimitedComplex.pm │ │ ├── LimitedNumeric.pm │ │ ├── NumberWithUnits.pm │ │ ├── Numeric.pm │ │ ├── PGcomplexmacros.pl │ │ └── README │ ├── List.pm │ ├── List │ │ ├── AbsoluteValue.pm │ │ ├── Interval.pm │ │ ├── List.pm │ │ ├── Matrix.pm │ │ ├── Point.pm │ │ ├── Set.pm │ │ └── Vector.pm │ ├── Number.pm │ ├── String.pm │ ├── UOP.pm │ ├── UOP │ │ ├── factorial.pm │ │ ├── minus.pm │ │ ├── plus.pm │ │ └── undefined.pm │ ├── Value.pm │ └── Variable.pm ├── Polynomial.pm ├── PowerPolynomial.pm ├── Regression.pm ├── Rserve.pm ├── SampleProblemParser.pm ├── Select.pm ├── Statistics.pm ├── Units.pm ├── Value.pm ├── Value │ ├── AnswerChecker.pm │ ├── Complex.pm │ ├── Context.pm │ ├── Context │ │ ├── Data.pm │ │ ├── Diagnostics.pm │ │ ├── Flags.pm │ │ └── Lists.pm │ ├── Formula.pm │ ├── Infinity.pm │ ├── Interval.pm │ ├── List.pm │ ├── Matrix.pm │ ├── Point.pm │ ├── Real.pm │ ├── Set.pm │ ├── String.pm │ ├── Union.pm │ ├── Vector.pm │ └── WeBWorK.pm ├── VectorField.pm ├── WWPlot.pm ├── WWSafe.pm └── WeBWorK │ ├── PG.pm │ └── PG │ ├── Constants.pm │ ├── ConvertToPGML.pm │ ├── Environment.pm │ ├── EquationCache.pm │ ├── IO.pm │ ├── ImageGenerator.pm │ ├── Localize.pm │ ├── Localize │ ├── cs-CZ.po │ ├── de.po │ ├── el.po │ ├── en.po │ ├── es.po │ ├── fr-CA.po │ ├── fr.po │ ├── he-IL.po │ ├── hu.po │ ├── ko.po │ ├── pg.pot │ ├── ru-RU.po │ ├── tr.po │ ├── zh-CN.po │ └── zh-HK.po │ ├── RestrictedClosureClass.pm │ ├── Tidy.pm │ └── Translator.pm ├── macros ├── PG.pl ├── PGcourse.pl ├── README.md ├── answers │ ├── ConditionalHint.pl │ ├── Generic.pl │ ├── PGasu.pl │ ├── PGfunctionevaluators.pl │ ├── PGmiscevaluators.pl │ ├── PGstringevaluators.pl │ ├── answerComposition.pl │ ├── answerCustom.pl │ ├── answerHints.pl │ ├── answerUtils.pl │ ├── answerVariableList.pl │ ├── extraAnswerEvaluators.pl │ └── unorderedAnswer.pl ├── capa │ ├── PG_CAPAmacros.pl │ ├── StdConst.pg │ └── StdUnits.pg ├── contexts │ ├── contextABCD.pl │ ├── contextAlternateDecimal.pl │ ├── contextAlternateIntervals.pl │ ├── contextArbitraryString.pl │ ├── contextBaseN.pl │ ├── contextBoolean.pl │ ├── contextComplexExtras.pl │ ├── contextComplexJ.pl │ ├── contextCongruence.pl │ ├── contextCurrency.pl │ ├── contextFiniteSolutionSets.pl │ ├── contextForm.pl │ ├── contextFraction.pl │ ├── contextFunctionAssign.pl │ ├── contextInequalities.pl │ ├── contextInequalitiesAllowStrings.pl │ ├── contextInequalitySetBuilder.pl │ ├── contextInteger.pl │ ├── contextIntegerFunctions.pl │ ├── contextLeadingZero.pl │ ├── contextLimitedComplex.pl │ ├── contextLimitedFactor.pl │ ├── contextLimitedNumeric.pl │ ├── contextLimitedPoint.pl │ ├── contextLimitedPolynomial.pl │ ├── contextLimitedPowers.pl │ ├── contextLimitedRadical.pl │ ├── contextLimitedRadicalComplex.pl │ ├── contextLimitedVector.pl │ ├── contextMatrixExtras.pl │ ├── contextOrdering.pl │ ├── contextPartition.pl │ ├── contextPercent.pl │ ├── contextPeriodic.pl │ ├── contextPermutation.pl │ ├── contextPermutationUBC.pl │ ├── contextPiecewiseFunction.pl │ ├── contextPolynomialFactors.pl │ ├── contextRationalExponent.pl │ ├── contextRationalFunction.pl │ ├── contextReaction.pl │ ├── contextRestrictedDomains.pl │ ├── contextScientificNotation.pl │ ├── contextString.pl │ ├── contextTF.pl │ ├── contextTrigDegrees.pl │ └── contextTypeset.pl ├── core │ ├── MathObjects.pl │ ├── PGML.pl │ ├── PGanswermacros.pl │ ├── PGauxiliaryFunctions.pl │ ├── PGbasicmacros.pl │ ├── PGcommonFunctions.pl │ ├── PGessaymacros.pl │ ├── PGgraders.pl │ ├── PGsequentialmacros.pl │ ├── PGstandard.pl │ ├── Parser.pl │ ├── RserveClient.pl │ ├── Value.pl │ ├── externalData.pl │ ├── sage.pl │ ├── scaffold.pl │ └── weightedGrader.pl ├── deprecated │ ├── Alfredmacros.pl │ ├── AnswerFormatHelp.pl │ ├── BrockPhysicsMacros.pl │ ├── CofIdaho_macros.pl │ ├── Dartmouthmacros.pl │ ├── MUHelp.pl │ ├── PGtextevaluators.pl │ ├── PeriodicRerandomization.pl │ ├── README.md │ ├── answerDiscussion.pl │ ├── compoundProblem.pl │ ├── compoundProblem2.pl │ ├── compoundProblem5.pl │ ├── freemanMacros.pl │ ├── hhAdditionalMacros.pl │ ├── littleneck.pl │ ├── problemPreserveAnswers.pl │ ├── problemRandomize.pl │ └── unionInclude.pl ├── graph │ ├── AppletObjects.pl │ ├── CanvasObject.pl │ ├── LiveGraphics3D.pl │ ├── LiveGraphicsCylindricalPlot3D.pl │ ├── LiveGraphicsParametricCurve3D.pl │ ├── LiveGraphicsParametricSurface3D.pl │ ├── LiveGraphicsRectangularPlot3D.pl │ ├── LiveGraphicsVectorField2D.pl │ ├── LiveGraphicsVectorField3D.pl │ ├── PCCgraphMacros.pl │ ├── PGanalyzeGraph.pl │ ├── PGgraphgrid.pl │ ├── PGgraphmacros.pl │ ├── PGlateximage.pl │ ├── PGnauGraphics.pl │ ├── PGstatisticsGraphMacros.pl │ ├── PGtikz.pl │ ├── VectorField2D.pl │ ├── imageChoice.pl │ ├── parserGraphTool.pl │ ├── plotly3D.pl │ └── unionImage.pl ├── math │ ├── LinearProgramming.pl │ ├── MatrixCheckers.pl │ ├── MatrixReduce.pl │ ├── MatrixUnimodular.pl │ ├── MatrixUnits.pl │ ├── PCCfactor.pl │ ├── PGcomplexmacros.pl │ ├── PGcomplexmacros2.pl │ ├── PGdiffeqmacros.pl │ ├── PGmatrixmacros.pl │ ├── PGmorematrixmacros.pl │ ├── PGnauBinpacking.pl │ ├── PGnauGraphCatalog.pl │ ├── PGnauGraphtheory.pl │ ├── PGnauScheduling.pl │ ├── PGnauSet.pl │ ├── PGnauStats.pl │ ├── PGnumericalmacros.pl │ ├── PGnumericevaluators.pl │ ├── PGpolynomialmacros.pl │ ├── PGstatisticsmacros.pl │ ├── SI_property_tables.pl │ ├── SolveLinearEquationPCC.pl │ ├── SystemsOfLinearEquationsProblemPCC.pl │ ├── VectorListCheckers.pl │ ├── algebraMacros.pl │ ├── bizarroArithmetic.pl │ ├── customizeLaTeX.pl │ ├── draggableProof.pl │ ├── draggableSubsets.pl │ ├── fixedPrecision.pl │ ├── interpMacros.pl │ ├── regrfnsPG.pl │ ├── specialTrigValues.pl │ ├── tableau.pl │ └── tableau_main_subroutines.pl ├── misc │ ├── PCCmacros.pl │ ├── PGunion.pl │ ├── randomPerson.pl │ ├── unionMacros.pl │ ├── unionMessages.pl │ ├── unionProblem.pl │ └── unionUtils.pl ├── parsers │ ├── parserAssignment.pl │ ├── parserAutoStrings.pl │ ├── parserCheckboxList.pl │ ├── parserCustomization.pl │ ├── parserDifferenceQuotient.pl │ ├── parserFormulaAnyVar.pl │ ├── parserFormulaUpToConstant.pl │ ├── parserFormulaWithUnits.pl │ ├── parserFunction.pl │ ├── parserFunctionPrime.pl │ ├── parserImplicitEquation.pl │ ├── parserImplicitPlane.pl │ ├── parserLinearInequality.pl │ ├── parserLinearRelation.pl │ ├── parserMultiAnswer.pl │ ├── parserMultiPart.pl │ ├── parserMultipleChoice.pl │ ├── parserNumberWithUnits.pl │ ├── parserOneOf.pl │ ├── parserParametricLine.pl │ ├── parserParametricPlane.pl │ ├── parserPopUp.pl │ ├── parserPrime.pl │ ├── parserQuotedString.pl │ ├── parserRadioButtons.pl │ ├── parserRadioMultiAnswer.pl │ ├── parserRoot.pl │ ├── parserSolutionFor.pl │ ├── parserUtils.pl │ ├── parserVectorUtils.pl │ └── parserWordCompletion.pl └── ui │ ├── PGchoicemacros.pl │ ├── PGinfo.pl │ ├── alignedChoice.pl │ ├── choiceUtils.pl │ ├── niceTables.pl │ ├── pccTables.pl │ ├── problemPanic.pl │ ├── quickMatrixEntry.pl │ ├── source.pl │ ├── text2PG.pl │ ├── unionLists.pl │ └── unionTables.pl ├── t ├── README.md ├── build_PG_envir.pl ├── conductingsphere.pg ├── contexts │ ├── alternative_intervals.t │ ├── baseN.t │ ├── fraction.t │ ├── integer.t │ ├── interval.t │ ├── toltype_digits.t │ └── trig_degrees.t ├── latex_image_test │ ├── latex_image_test1.pg │ └── latex_image_test2.pg ├── linebreak_at_commas_example.pg ├── macros │ ├── basicmacros.t │ ├── load_macros.t │ ├── math_objects_basics.t │ ├── math_objects_more.t │ ├── numerical_methods.t │ ├── pgaux.t │ └── tableau.t ├── macros_misc │ └── random_person.t ├── math_objects │ └── factorial.t ├── matrix_tableau_tests │ ├── linebreaks.pg │ ├── matrix_test1.pg │ ├── matrix_test2.pg │ ├── print_tableau_Test.pg │ ├── tableau_javascript.pg │ ├── tableau_test1.pg │ └── tableau_test2.pg ├── output_macros │ ├── niceTables.pg │ └── niceTables.t ├── pg_problems │ ├── problem_file.t │ └── source.t ├── pg_test_problems │ ├── alternateDecimalContext.pg │ ├── arabic_test.pg │ ├── badlibraryproblems.txt │ ├── blankProblem.pg │ ├── conductingsphere.pg │ ├── goodlibraryproblems.txt │ ├── inequalitySetBuilderContext.pg │ ├── payer_test.pg │ ├── pg_uses_cmplx_cmp.list │ └── settest_notnull_and_prettyprint │ │ ├── listVariables.pg │ │ ├── pretty_print_html.pg │ │ ├── pretty_print_tex.pg │ │ ├── pretty_print_text.pg │ │ ├── settest_notnull_and_prettyprint.def │ │ └── test_not_nullpg.pg ├── test_find_file_in_directories.pl ├── tikz_test │ ├── tikz_image.t │ ├── tikz_test1.pg │ ├── tikz_test2.pg │ └── tikz_test3.pg └── units │ ├── add_units.t │ ├── basic_module.t │ ├── basic_parser.t │ ├── conversions.t │ ├── electromagnetic.t │ ├── electron_volts.t │ ├── length.t │ ├── radiation.t │ ├── time.t │ └── volume.t └── tutorial ├── css └── sample-problem.css ├── js └── PG.js ├── sample-problems ├── Algebra │ ├── AlgebraicFractionAnswer.pg │ ├── AnswerBlankInExponent.pg │ ├── AnswerUpToMultiplication.pg │ ├── DomainRange.pg │ ├── DynamicGraph.pg │ ├── EquationDefiningFunction.pg │ ├── EquationImplicitFunction.pg │ ├── ExpandedPolynomial.pg │ ├── FactoredPolynomial.pg │ ├── FractionAnswer.pg │ ├── FunctionDecomposition.pg │ ├── GraphToolCircle.pg │ ├── GraphToolCubic.pg │ ├── GraphToolCustomChecker.pg │ ├── GraphToolLine.pg │ ├── GraphToolNumberLine.pg │ ├── GraphToolPoints.pg │ ├── InequalityAnswer.pg │ ├── LinearInequality.pg │ ├── Logarithms.pg │ ├── PointAnswers.pg │ ├── ScalingTranslating.pg │ ├── SimpleFactoring.pg │ ├── SolutionForEquation.pg │ ├── StringOrOtherType.pg │ ├── TableOfValues.pg │ └── UnorderedAnswers.pg ├── Complex │ ├── ComplexOperations.pg │ ├── LimitedComplex.pg │ └── OtherOperations.pg ├── DiffCalc │ ├── AnswerWithUnits.pg │ ├── DifferenceQuotient.pg │ ├── DifferentiateFunction.pg │ └── LinearApprox.pg ├── DiffCalcMV │ ├── ContourPlot.pg │ └── ImplicitPlane.pg ├── DiffEq │ ├── GeneralSolutionODE.pg │ ├── HeavisideStep.pg │ └── PrimesInFormulas.pg ├── IntegralCalc │ ├── DoubleIntegral.pg │ ├── GraphShading.pg │ ├── IndefiniteIntegrals.pg │ ├── LimitsOfIntegration.pg │ ├── RiemannSums.pg │ └── VolumeOfRevolution.pg ├── LinearAlgebra │ ├── MatrixAnswer1.pg │ ├── MatrixAnswer2.pg │ ├── MatrixCustomAnswerChecker.pg │ ├── MatrixOperations.pg │ └── RowOperations.pg ├── Misc │ ├── ChemicalReaction.pg │ ├── DraggableProof.pg │ ├── DynamicGraphPolygon.pg │ ├── EssayAnswer.pg │ ├── FormulaAnswer.pg │ ├── FormulaDomain.pg │ ├── FormulaTestPoints.pg │ ├── IframeEmbedding.pg │ ├── ManyMultipleChoice.pg │ ├── Matching.pg │ ├── MatchingAlt.pg │ ├── MatchingGraphs.pg │ ├── MultipleChoiceCheckbox.pg │ ├── MultipleChoicePopup.pg │ ├── MultipleChoiceRadio.pg │ ├── RandomPerson.pg │ └── Scaffolding.pg ├── Parametric │ ├── ParametricEquationAnswers.pg │ ├── ParametricPlot.pg │ ├── PolarGraph.pg │ ├── SpaceCurveGraph.pg │ ├── Spacecurve.pg │ ├── SurfaceGraph.pg │ ├── VectorParametricDerivative.pg │ ├── VectorParametricFunction.pg │ └── VectorParametricLines.pg ├── README.md ├── Sequences │ ├── AnswerOrderedList.pg │ ├── ExplicitSequence.pg │ ├── RecursiveSequence.pg │ └── SeriesTest.pg ├── Statistics │ ├── linearRegression.pg │ └── meanStdDev.pg ├── Trig │ ├── DisableFunctionsTrig.pg │ ├── DraggableIdentity.pg │ ├── PeriodicAnswers.pg │ ├── ProvingTrigIdentities.pg │ ├── SpecialTrigValues.pg │ ├── TrigDegrees.pg │ └── TrigIdentities.pg ├── VectorCalc │ ├── CylindricalGraph3D.pg │ ├── DirectionField.pg │ ├── RectangularGraph3D.pg │ ├── VectorFieldGraph2D.pg │ ├── VectorFieldGraph3D │ │ ├── VectorFieldGraph3D1.pg │ │ └── exploding-vector-field.png │ ├── VectorLineSegment1.pg │ ├── VectorLineSegment2.pg │ ├── VectorOperations.pg │ ├── VectorParametricLine.pg │ └── Vectors.pg ├── problem-techniques │ ├── AdaptiveParameters.pg │ ├── AddingFunctions.pg │ ├── AnswerHints.pg │ ├── AnswerInExponent.pg │ ├── AnswerIsSolutionToEquation.pg │ ├── AnyAnswerMarkedCorrect.pg │ ├── CalculatingWithPoints.pg │ ├── ComposingFunctions.pg │ ├── ConstantsInProblems.pg │ ├── CustomAnswerCheckers.pg │ ├── CustomAnswerListChecker.pg │ ├── DataTables.pg │ ├── DifferentiatingFormulas.pg │ ├── DigitsTolType.pg │ ├── DisableFunctions.pg │ ├── DraggableSubsets.pg │ ├── EquationEvaluators.pg │ ├── EquationsDefiningFunctions.pg │ ├── ErrorMessageCustomization.pg │ ├── EvalVersusSubstitute.pg │ ├── ExtractingCoordinatesFromPoint.pg │ ├── FactoringAndExpanding.pg │ ├── FormattingDecimals.pg │ ├── FormulasToConstants.pg │ ├── GraphsInTables.pg │ ├── HtmlLinks.pg │ ├── Images.pg │ ├── InequalityEvaluators.pg │ ├── IntervalEvaluators.pg │ ├── Knowls.pg │ ├── LayoutTable.pg │ ├── Multianswer.pg │ ├── NumericalTolerance.pg │ ├── OtherVariables.pg │ ├── Percent.pg │ ├── RandomFunction.pg │ ├── RestrictAnswerToFraction.pg │ ├── RestrictingFunctions.pg │ ├── SimplePopUp.pg │ ├── StaticImages.pg │ ├── StringsInContext.pg │ ├── TikZImages.pg │ ├── WeightedGrader.pg │ ├── image.png │ └── local.html └── snippets │ └── CommentsForInstructors.pg └── templates ├── general-layout.mt ├── general-main.mt ├── general-sidebar.mt └── problem-template.mt /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | max_line_length = 120 8 | trim_trailing_whitespace = true 9 | indent_style = tab 10 | indent_size = 4 11 | 12 | [*.yml] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.pg] 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [openwebwork] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/check-formats.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Check Formatting of Code Base 3 | 4 | defaults: 5 | run: 6 | shell: bash 7 | 8 | on: 9 | push: 10 | branches-ignore: [main, develop] 11 | pull_request: 12 | 13 | jobs: 14 | perltidy: 15 | name: Check Perl file formatting with perltidy 16 | runs-on: ubuntu-22.04 17 | container: 18 | image: perl:5.34 19 | steps: 20 | - name: Checkout code 21 | uses: actions/checkout@v4 22 | - name: Install dependencies 23 | run: cpanm -n Perl::Tidy@20220613 24 | - name: Run perltidy 25 | shell: bash 26 | run: | 27 | git config --global --add safe.directory "$GITHUB_WORKSPACE" 28 | shopt -s extglob globstar nullglob 29 | perltidy --pro=./.perltidyrc -b -bext='/' ./**/*.p[lm] ./**/*.t && git diff --exit-code 30 | 31 | prettier: 32 | name: Check JavaScript, style, and HTML file formatting with prettier 33 | runs-on: ubuntu-22.04 34 | steps: 35 | - name: Checkout code 36 | uses: actions/checkout@v4 37 | - name: Install Node 38 | uses: actions/setup-node@v4 39 | with: 40 | node-version: '20' 41 | - name: Install Dependencies 42 | run: cd htdocs && npm ci --ignore-scripts 43 | - name: Check formatting with prettier 44 | run: cd htdocs && npm run prettier-check 45 | -------------------------------------------------------------------------------- /.github/workflows/unit-tests.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Unit Tests 3 | 4 | on: 5 | push: 6 | pull_request: 7 | 8 | jobs: 9 | unit-tests: 10 | runs-on: ubuntu-22.04 11 | steps: 12 | - name: Checkout PG code 13 | uses: actions/checkout@v4 14 | 15 | - name: Install Ubuntu dependencies 16 | run: | 17 | sudo apt-get update 18 | sudo apt-get install -y --no-install-recommends --no-install-suggests \ 19 | cpanminus \ 20 | dvipng \ 21 | dvisvgm \ 22 | imagemagick \ 23 | libclass-accessor-perl \ 24 | libclass-tiny-perl \ 25 | libdbi-perl \ 26 | libencode-perl \ 27 | libgd-perl \ 28 | libhtml-parser-perl \ 29 | libjson-perl \ 30 | libjson-xs-perl \ 31 | liblocale-maketext-lexicon-perl \ 32 | libmojolicious-perl \ 33 | libtest2-suite-perl \ 34 | libtie-ixhash-perl \ 35 | libuuid-tiny-perl \ 36 | libyaml-libyaml-perl \ 37 | pdf2svg \ 38 | texlive \ 39 | texlive-latex-extra \ 40 | texlive-latex-recommended \ 41 | texlive-plain-generic 42 | 43 | - name: Install Perl dependencies 44 | run: cpanm --sudo -fi --notest HTML::TagParser 45 | 46 | - name: Run Perl unit tests 47 | run: | 48 | export PG_ROOT=`pwd` 49 | prove -r t 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | *.bak 4 | 5 | conf/pg_config.yml 6 | cover_db/ 7 | 8 | htdocs/node_modules 9 | htdocs/static-assets.json 10 | htdocs/**/*.min.js 11 | htdocs/**/*.min.css 12 | htdocs/tmp 13 | -------------------------------------------------------------------------------- /.perltidyrc: -------------------------------------------------------------------------------- 1 | # PBP .perltidyrc file 2 | -l=120 # Max line width is 120 cols 3 | -et=4 # Use tabs instead of spaces. 4 | -i=4 # Indent level is 4 cols 5 | -ci=4 # Continuation indent is 4 cols 6 | -b # Write the file inline and create a .bak file 7 | -vt=0 # Minimal vertical tightness 8 | -cti=0 # No extra indentation for closing brackets 9 | -pt=2 # Maximum parenthesis tightness 10 | -bt=1 # Medium brace tightness 11 | -sbt=1 # Medium square bracket tightness 12 | -bbt=1 # Medium block brace tightness 13 | -nsfs # No space before semicolons 14 | -nolq # Don't outdent long quoted strings 15 | -mbl=1 # Do not allow multiple empty lines 16 | -ce # Cuddled else 17 | -cb # Cuddled blocks 18 | -nbbc # Do not add blank lines before full length comments 19 | -nbot # No line break on ternary 20 | -nlop # No logical padding (this causes mixed tabs and spaces) 21 | -wn # Weld nested containers 22 | -xci # Extended continuation indentation 23 | -vxl='q' # No vertical alignment of qw quotes 24 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSpacing": true, 4 | "printWidth": 120, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "none" 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | WeBWorK 2 | Online Homework Delivery System 3 | Version 2.* 4 | 5 | Copyright 2000-2024, The WeBWorK Project 6 | All rights reserved. 7 | 8 | This program is free software; you can redistribute it and/or modify 9 | it under the terms of either: 10 | 11 | a) the GNU General Public License as published by the Free 12 | Software Foundation; either version 2, or (at your option) 13 | any later version, or 14 | 15 | b) the "Artistic License" which comes with this package. 16 | 17 | This program is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either 20 | the GNU General Public License or the Artistic License for more details. 21 | 22 | You should have received a copy of the Artistic License with this 23 | package, in the file named "Artistic". If not, we'll be glad to provide 24 | one. 25 | 26 | You should also have received a copy of the GNU General Public License 27 | along with this program in the file named "Copying". If not, write to the 28 | Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 29 | 02111-1307, USA or visit their web page on the internet at 30 | http://www.gnu.org/copyleft/gpl.html. 31 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | WeBWorK 2 | Program Generation Language 3 | Version 2.* 4 | Branch: https://github.com/openwebwork 5 | 6 | 7 | http://webwork.maa.org/wiki/Category:Release_Notes 8 | 9 | Copyright 2000-2024, The WeBWorK Project 10 | http://webwork.maa.org 11 | All rights reserved. 12 | -------------------------------------------------------------------------------- /README_maketext: -------------------------------------------------------------------------------- 1 | ##### README 2 | 3 | 4 | Currently (version 1.25) Maketext has a "use strict" line as part of its string parser. This "use strict" interacts badly with WWSafe and fails. Unfortunately the use strict cannot be turned off in Maketext. This means that you can use maketext in PG, but you cannot use any of its features, including simple ones like maketext('Hello [_1] World','Cruel'); 5 | 6 | If it becomes necessary to use the more advance versions of maketext in PG we will need to fork Maketext. 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | $PG_VERSION ='2.19'; 2 | $PG_COPYRIGHT_YEARS = '1996-2024'; 3 | 4 | 1; 5 | -------------------------------------------------------------------------------- /assets/tex/CAPA.tex: -------------------------------------------------------------------------------- 1 | % capa tex macros 2 | 3 | \newcommand{\capa}{{\sl C\kern-.10em\raise-.00ex\hbox{\rm A}\kern-.22em% 4 | {\sl P}\kern-.14em\kern-.01em{\rm A}}} 5 | 6 | \newenvironment{choicelist} 7 | {\begin{list}{} 8 | {\setlength{\rightmargin}{0in}\setlength{\leftmargin}{0.13in} 9 | \setlength{\topsep}{0.05in}\setlength{\itemsep}{0.022in} 10 | \setlength{\parsep}{0in}\setlength{\belowdisplayskip}{0.04in} 11 | \setlength{\abovedisplayskip}{0.05in} 12 | \setlength{\abovedisplayshortskip}{-0.04in} 13 | \setlength{\belowdisplayshortskip}{0.04in}} 14 | } 15 | {\end{list}} 16 | -------------------------------------------------------------------------------- /bin/perltidy-pg.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | ################################################################################ 3 | # WeBWorK Online Homework Delivery System 4 | # Copyright © 2000-2024 The WeBWorK Project, https://github.com/openwebwork 5 | # 6 | # This program is free software; you can redistribute it and/or modify it under 7 | # the terms of either: (a) the GNU General Public License as published by the 8 | # Free Software Foundation; either version 2, or (at your option) any later 9 | # version, or (b) the "Artistic License" which comes with this package. 10 | # 11 | # This program is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the 14 | # Artistic License for more details. 15 | ################################################################################ 16 | 17 | =head1 NAME 18 | 19 | pg-perltidy.pl -- Run perltidy on pg problem files. 20 | 21 | =head1 SYNOPSIS 22 | 23 | pg-perltidy.pl [options] file1 file2 ... 24 | 25 | =head1 DESCRIPTION 26 | 27 | Run perltidy on pg problem files. 28 | 29 | =head1 OPTIONS 30 | 31 | This script accepts all of the options that are accepted by perltidy. See the 32 | perltidy documentation for details. 33 | 34 | Note that if the -pro=file option is not given, then this script will attempt to 35 | use the perltidy-pg.rc file in the PG bin directory for this option. For this to 36 | work the the perltidy-pg.rc file in the PG bin directory must be readable. 37 | 38 | =cut 39 | 40 | use strict; 41 | use warnings; 42 | 43 | use Perl::Tidy; 44 | use Mojo::File qw(curfile); 45 | 46 | use lib curfile->dirname->dirname . '/lib'; 47 | 48 | use WeBWorK::PG::Tidy qw(pgtidy); 49 | 50 | pgtidy(); 51 | 52 | 1; 53 | -------------------------------------------------------------------------------- /bin/perltidy-pg.rc: -------------------------------------------------------------------------------- 1 | # PBP .perltidyrc file 2 | -l=80 # Max line width is 80 cols 3 | -i=4 # Indent level is 4 cols 4 | -ci=4 # Continuation indent is 4 cols 5 | -b # Write the file inline and create a .bak file 6 | -vt=0 # Minimal vertical tightness 7 | -cti=0 # No extra indentation for closing brackets 8 | -pt=2 # Maximum parenthesis tightness 9 | -bt=1 # Medium brace tightness 10 | -sbt=1 # Medium square bracket tightness 11 | -bbt=1 # Medium block brace tightness 12 | -nsfs # No space before semicolons 13 | -nolq # Don't outdent long quoted strings 14 | -mbl=1 # Do not allow multiple empty lines 15 | -ce # Cuddled else 16 | -cb # Cuddled blocks 17 | -nbbc # Do not add blank lines before full length comments 18 | -nbot # No line break on ternary 19 | -nlop # No logical padding (this causes mixed tabs and spaces) 20 | -wn # Weld nested containers 21 | -xci # Extended continuation indentation 22 | -vxl='q' # No vertical alignment of qw quotes 23 | -------------------------------------------------------------------------------- /bin/update-localization-files: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function print_help_exit 4 | { 5 | printf "Usage: %s [options]\n" $(basename $0) >&2 6 | printf " Update the pg.pot and language .po files with translation strings from the code.\n" >&2 7 | printf " options:\n" >&2 8 | printf " -p|--po-update Update po files as well. By default only the pg.pot file is updated.\n" >&2 9 | printf " -l|--langauge Update the only given language in addition to updating the pg.pot file.\n" >&2 10 | printf " -h|--help Show this help.\n" >&2 11 | exit 1 12 | } 13 | 14 | TEMP=$(getopt -a -o pl:h -l po-update,language:,help -n "$(basename $0)" -- "$@") 15 | 16 | eval set -- "$TEMP" 17 | 18 | UPDATE_PO=false 19 | LANGUAGE="" 20 | 21 | while [ ! "$1" = "--" ] 22 | do 23 | case "$1" in 24 | -p|--po-update) 25 | UPDATE_PO=true 26 | shift 1 27 | ;; 28 | -l|--language) 29 | LANGUAGE=$2 30 | shift 2 31 | ;; 32 | -h|--help) 33 | print_help_exit 34 | ;; 35 | *) 36 | echo "Internal error!" 37 | exit 1 38 | ;; 39 | esac 40 | done 41 | 42 | if [ -z "$PG_ROOT" ]; then 43 | echo >&2 "You need to set the PG_ROOT environment variable. Aborting." 44 | exit 1 45 | fi 46 | 47 | command -v xgettext.pl >/dev/null 2>&1 || { 48 | echo >&2 "xgettext.pl needs to be installed. It is inlcuded in the perl package Locale::Maketext::Extract. Aborting."; 49 | exit 1; 50 | } 51 | 52 | LOCDIR=$PG_ROOT/lib/WeBWorK/PG/Localize 53 | 54 | cd $LOCDIR 55 | 56 | echo "Updating $LOCDIR/pg.pot" 57 | 58 | xgettext.pl -o pg.pot -D $PG_ROOT/lib -D $PG_ROOT/macros 59 | 60 | if $UPDATE_PO; then 61 | find $LOCDIR -name '*.po' -exec bash -c "echo \"Updating {}\"; msgmerge -qUN {} pg.pot" \; 62 | elif [[ $LANGUAGE != "" && -e "$LANGUAGE.po" ]]; then 63 | echo "Updating $LOCDIR/$LANGUAGE.po" 64 | msgmerge -qUN $LANGUAGE.po pg.pot 65 | fi 66 | -------------------------------------------------------------------------------- /cpanfile: -------------------------------------------------------------------------------- 1 | # Install required module dependancies listed here (runtime and test) with 2 | # cpanm --installdeps . 3 | 4 | on runtime => sub { 5 | requires 'perl' => '5.20.3'; 6 | recommends 'perl' => '5.30.0'; # Needed for Statistics::R::IO 7 | 8 | requires 'DBI'; 9 | requires 'Digest::MD5'; 10 | requires 'Class::Accessor'; 11 | requires 'Encode'; 12 | requires 'Encode::Encoding'; 13 | requires 'Exporter'; 14 | requires 'GD'; 15 | requires 'HTML::Entities'; 16 | requires 'HTML::Parser'; 17 | requires 'JSON'; 18 | requires 'JSON::XS'; 19 | requires 'Locale::Maketext'; 20 | requires 'Locale::Maketext::Lexicon'; 21 | requires 'Mojolicious'; 22 | requires 'Tie::IxHash'; 23 | requires 'Types::Serialiser'; 24 | requires 'UUID::Tiny'; 25 | requires 'YAML::XS'; 26 | 27 | # Needed for Rserve 28 | recommends 'Statistics::R::IO::Rserve'; 29 | recommends 'Class::Tiny'; 30 | recommends 'IO::Handle'; 31 | }; 32 | 33 | on test => sub { 34 | requires 'Test2::V0' => '0.000139'; 35 | requires 'HTML::TagParser'; # Used by t/macros/basicmacros.t 36 | 37 | recommends 'Data::Dumper'; # For debugging data structures 38 | recommends 'Test2::Tools::Explain'; # For debugging data structures 39 | }; 40 | 41 | # Install development dependancies with 42 | # cpanm --installdeps --with-develop --with-recommends . 43 | 44 | on develop => sub { 45 | recommends 'Module::CPANfile'; 46 | recommends 'Test::CPANfile'; # Verifies this file has all the dependancies 47 | }; 48 | -------------------------------------------------------------------------------- /doc/MathObjects/problems/sample01.pg: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | # 3 | # Example showing how to use MathObjects to make 4 | # a formula that you can evaluate and print in TeX form. 5 | # 6 | 7 | DOCUMENT(); # This should be the first executable line in the problem. 8 | 9 | loadMacros( 10 | "PGstandard.pl", 11 | "MathObjects.pl", 12 | ); 13 | 14 | TEXT(beginproblem()); 15 | 16 | ########################################################### 17 | # 18 | # Use standard numeric mode 19 | # 20 | Context('Numeric'); 21 | 22 | # 23 | # Define some functions 24 | # 25 | $a = non_zero_random(-8,8,1); 26 | $b = random(1,8,1); 27 | 28 | @f = ( 29 | "1 + $a*x + $b x^2", 30 | "$a / (1 + $b x)", 31 | "$a x^3 + $b", 32 | "($a - x) / ($b + x^2)" 33 | ); 34 | 35 | # 36 | # Pick one at random 37 | # 38 | $k = random(0,$#f,1); 39 | $f = Formula($f[$k])->reduce; 40 | 41 | # 42 | # Where to evaluate it 43 | # 44 | $x = random(-5,5,1); 45 | 46 | ########################################################### 47 | # 48 | # The problem text 49 | # 50 | BEGIN_TEXT 51 | 52 | If \(\displaystyle f(x) = \{$f->TeX\}\) then \(f($x)=\) \{ans_rule(10)\}. 53 | 54 | END_TEXT 55 | 56 | ########################################################### 57 | # 58 | # The answer 59 | # 60 | ANS($f->eval(x=>$x)->cmp); 61 | $showPartialCorrectAnswers = 1; 62 | 63 | ########################################################### 64 | 65 | ENDDOCUMENT(); # This should be the last executable line in the problem. 66 | -------------------------------------------------------------------------------- /doc/MathObjects/problems/sample02.pg: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | # 3 | # Example showing how you can use perl expressions (not 4 | # just character strings) to generate formulas. 5 | # 6 | 7 | DOCUMENT(); # This should be the first executable line in the problem. 8 | 9 | loadMacros( 10 | "PGstandard.pl", 11 | "MathObjects.pl", 12 | ); 13 | 14 | TEXT(beginproblem()); 15 | 16 | ########################################################### 17 | # 18 | # Use standard numeric mode 19 | # 20 | Context('Numeric'); 21 | $x = Formula('x'); # used to construct formulas below. 22 | 23 | # 24 | # Define a function and its derivative and make them pretty 25 | # 26 | $a = random(1,8,1); 27 | $b = random(-8,8,1); 28 | $c = random(-8,8,1); 29 | 30 | $f = ($a*$x**2 + $b*$x + $c) -> reduce; 31 | $df = (2*$a*$x + $b) -> reduce; 32 | 33 | $x = random(-8,8,1); 34 | 35 | ########################################################### 36 | # 37 | # The problem text 38 | # 39 | BEGIN_TEXT 40 | 41 | Suppose \(f(x) = \{$f->TeX\}\). 42 | $PAR 43 | Then \(f'(x)=\) \{ans_rule(20)\},$BR 44 | and \(f'($x)=\) \{ans_rule(20)\}. 45 | 46 | END_TEXT 47 | 48 | ########################################################### 49 | # 50 | # The answers 51 | # 52 | ANS(df->cmp); 53 | ANS($df->eval(x=>$x)->cmp); 54 | $showPartialCorrectAnswers = 1; 55 | 56 | ########################################################### 57 | 58 | ENDDOCUMENT(); # This should be the last executable line in the problem. 59 | -------------------------------------------------------------------------------- /doc/MathObjects/problems/sample03.pg: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | # 3 | # Example showing how to use the MathObject library's 4 | # differentiation capabilities. 5 | # 6 | 7 | DOCUMENT(); # This should be the first executable line in the problem. 8 | 9 | loadMacros( 10 | "PGstandard.pl", 11 | "MathObjects.pl", 12 | ); 13 | 14 | TEXT(beginproblem()); 15 | 16 | ########################################################### 17 | # 18 | # Use standard numeric mode 19 | # 20 | Context('Numeric'); 21 | $x = Formula('x'); # used to construct formulas below. 22 | 23 | # 24 | # Define a function and its derivative and make them pretty 25 | # 26 | $a = random(1,8,1); 27 | $b = random(-8,8,1); 28 | $c = random(-8,8,1); 29 | 30 | $f = ($a*$x**2 + $b*$x + $c) -> reduce; 31 | $df = $f->D('x'); 32 | 33 | $x = random(-8,8,1); 34 | 35 | ########################################################### 36 | # 37 | # The problem text 38 | # 39 | BEGIN_TEXT 40 | 41 | Suppose \(f(x) = \{$f->TeX\}\). 42 | $PAR 43 | Then \(f'(x)=\) \{ans_rule(20)\},$BR 44 | and \(f'($x)=\) \{ans_rule(20)\}. 45 | $PAR 46 | (Same as previous problem, but using the formal differentiation package. 47 | Note that automatic differentiation does not always produce the simplest form.) 48 | 49 | END_TEXT 50 | 51 | ########################################################### 52 | # 53 | # The answers 54 | # 55 | ANS($df->cmp); 56 | ANS($df->eval(x=>$x)->cmp); 57 | $showPartialCorrectAnswers = 1; 58 | 59 | ########################################################### 60 | 61 | ENDDOCUMENT(); # This should be the last executable line in the problem. 62 | -------------------------------------------------------------------------------- /doc/MathObjects/problems/sample06.pg: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | # 3 | # Example showing how to use the Parser to make 4 | # a formula that you can evaluate and print in TeX form. 5 | # 6 | 7 | DOCUMENT(); # This should be the first executable line in the problem. 8 | 9 | loadMacros( 10 | "PGstandard.pl", 11 | "MathObjets.pl", 12 | ); 13 | 14 | TEXT(beginproblem()); 15 | 16 | ########################################################### 17 | # 18 | # The setup 19 | # 20 | Context('Vector'); 21 | 22 | # 23 | # Define a vector 24 | # 25 | $a = non_zero_random(-8,8,1); 26 | $b = non_zero_random(-8,8,1); 27 | $c = non_zero_random(-8,8,1); 28 | 29 | $V = $a*i + $b*j + $c*k; # equivalently: $V = Vector($a,$b,$c); 30 | 31 | ########################################################### 32 | # 33 | # The problem text 34 | # 35 | 36 | Context()->texStrings; 37 | BEGIN_TEXT 38 | 39 | The length of the vector \($V\) is \{ans_rule(20)\}. 40 | 41 | END_TEXT 42 | Context()->normalStrings; 43 | 44 | ########################################################### 45 | # 46 | # The answer 47 | # 48 | 49 | ANS(norm($V)->cmp); 50 | $showPartialCorrectAnswers = 1; 51 | 52 | ########################################################### 53 | 54 | ENDDOCUMENT(); # This should be the last executable line in the problem. 55 | -------------------------------------------------------------------------------- /doc/MathObjects/problems/sample07.pg: -------------------------------------------------------------------------------- 1 | ########################################################## 2 | # 3 | # Example showing how to use the built-in answer checker 4 | # for Real MathObjects. 5 | # 6 | 7 | DOCUMENT(); # This should be the first executable line in the problem. 8 | 9 | loadMacros( 10 | "PGstandard.pl", 11 | "MathObjects.pl", 12 | ); 13 | 14 | TEXT(beginproblem()); 15 | 16 | ########################################################## 17 | # 18 | # The setup 19 | # 20 | 21 | Context('Numeric'); 22 | 23 | $a = Real(random(2,6,1)); 24 | $b = Real(random($a+1,$a+8,1)); 25 | 26 | $c = sqrt($a**2 + $b**2); # still a Real object 27 | 28 | ########################################################## 29 | # 30 | # The problem text 31 | # 32 | 33 | Context()->texStrings; 34 | BEGIN_TEXT 35 | 36 | Suppose the legs of a triangle are of length \($a\) and \($b\).$BR 37 | Then the hypoteneuse is of length \{ans_rule(20)\}. 38 | 39 | END_TEXT 40 | Context()->normalStrings(); 41 | 42 | ########################################################### 43 | # 44 | # The answer 45 | # 46 | 47 | ANS($c->cmp); 48 | 49 | ########################################################### 50 | 51 | ENDDOCUMENT(); # This should be the last executable line in the problem. 52 | -------------------------------------------------------------------------------- /doc/MathObjects/problems/sample08.pg: -------------------------------------------------------------------------------- 1 | ########################################################## 2 | # 3 | # Example showing how to use the built-in answer checker 4 | # for Complex MathObjects. 5 | # 6 | 7 | DOCUMENT(); # This should be the first executable line in the problem. 8 | 9 | loadMacros( 10 | "PGstandard.pl", 11 | "MathObjects.pl", 12 | ); 13 | 14 | TEXT(beginproblem()); 15 | 16 | ########################################################## 17 | # 18 | # The setup 19 | # 20 | 21 | Context('Complex'); 22 | 23 | $z = random(-5,5,1) + non_zero_random(-5,5,1)*i; 24 | 25 | $f = Formula('z^2 + 2z - 1'); 26 | $fz = $f->eval(z => $z); 27 | 28 | ########################################################## 29 | # 30 | # The problem text 31 | # 32 | 33 | Context()->texStrings; 34 | BEGIN_TEXT 35 | 36 | Suppose \(f(z) = $f\).$BR 37 | Then \(f($z)\) = \{ans_rule(20)\}. 38 | 39 | END_TEXT 40 | Context()->normalStrings; 41 | 42 | ########################################################### 43 | # 44 | # The answer 45 | # 46 | 47 | ANS($fz->cmp); 48 | 49 | ########################################################### 50 | 51 | ENDDOCUMENT(); # This should be the last executable line in the problem. 52 | -------------------------------------------------------------------------------- /doc/MathObjects/problems/sample09.pg: -------------------------------------------------------------------------------- 1 | ########################################################## 2 | # 3 | # Example showing how to use the built-in answer checker 4 | # for Point MathObjects. 5 | # 6 | 7 | DOCUMENT(); # This should be the first executable line in the problem. 8 | 9 | loadMacros( 10 | "PGstandard.pl", 11 | "MathObjects.pl", 12 | ); 13 | 14 | TEXT(beginproblem()); 15 | 16 | ########################################################## 17 | # 18 | # The setup 19 | # 20 | 21 | Context('Point'); 22 | 23 | $P1 = Point(-2,4,2); 24 | $P2 = Point(2,-3,1); 25 | 26 | $M = ($P1+$P2)/2; # still a Point object 27 | 28 | ########################################################## 29 | # 30 | # The problem text 31 | # 32 | 33 | Context()->texStrings; 34 | BEGIN_TEXT 35 | 36 | The midpoint of the line segment from \($P1\) to \($P2\) 37 | is \{ans_rule(20)\}. 38 | 39 | END_TEXT 40 | Context()->normalStrings; 41 | 42 | ########################################################### 43 | # 44 | # The answer 45 | # 46 | 47 | ANS($M->cmp); 48 | 49 | ########################################################### 50 | 51 | ENDDOCUMENT(); # This should be the last executable line in the problem. 52 | -------------------------------------------------------------------------------- /doc/MathObjects/problems/sample10.pg: -------------------------------------------------------------------------------- 1 | ########################################################## 2 | # 3 | # Example showing how to use the built-in answer checker 4 | # for Vector MathObjects, and control the display of vectors. 5 | # 6 | 7 | DOCUMENT(); # This should be the first executable line in the problem. 8 | 9 | loadMacros( 10 | "PGstandard.pl", 11 | "MathObjects.pl", 12 | ); 13 | 14 | TEXT(beginproblem()); 15 | 16 | ########################################################## 17 | # 18 | # The setup 19 | # 20 | 21 | Context('Vector'); 22 | 23 | $P1 = Point(1,random(-3,3,1),random(-3,3,1)); 24 | $P2 = Point(random(-3,3,1),4,random(-3,3,1)); 25 | 26 | $V = Vector($P2-$P1); # convert a point to a vector 27 | 28 | Context()->flags->set(ijk=>0); # vectors are shown in <,,> notation 29 | Context()->constants->add(a=>1,b=>1,c=>1); 30 | 31 | $ABC = Formula(""); 32 | 33 | ########################################################## 34 | # 35 | # The problem text 36 | # 37 | 38 | Context()->texStrings; 39 | BEGIN_TEXT 40 | The vector from \($P1\) to \($P2\) is \{ans_rule(20)\}. 41 | $PAR 42 | You can use either \($ABC\) or \(\{$ABC->ijk\}\) notation,$BR 43 | and can perform vector operations to produce your answer. 44 | END_TEXT 45 | Context()->normalStrings; 46 | 47 | ########################################################### 48 | # 49 | # The answer 50 | # 51 | 52 | ANS($V->cmp(promotePoints=>1)); # allow answers to be points or vectors 53 | 54 | ########################################################### 55 | 56 | ENDDOCUMENT(); # This should be the last executable line in the problem. 57 | -------------------------------------------------------------------------------- /doc/MathObjects/problems/sample11.pg: -------------------------------------------------------------------------------- 1 | ########################################################## 2 | # 3 | # Example showing how to use the built-in answer checker 4 | # for Interval MathObjects. 5 | # 6 | 7 | DOCUMENT(); # This should be the first executable line in the problem. 8 | 9 | loadMacros( 10 | "PGstandard.pl", 11 | "MathObjects.pl", 12 | ); 13 | 14 | TEXT(beginproblem()); 15 | 16 | ########################################################## 17 | # 18 | # The setup 19 | # 20 | 21 | Context('Interval'); 22 | 23 | $p1 = random(-5,2,1); 24 | $p2 = random($p1+1,$p1+7,1); 25 | 26 | $f = Formula("x^2 - ($p1+$p2) x + $p1*$p2")->reduce; 27 | $I = Interval("($p1,$p2)"); 28 | 29 | ########################################################## 30 | # 31 | # The problem text 32 | # 33 | 34 | Context()->texStrings; 35 | BEGIN_TEXT 36 | The function \(f(x) = $f\) is negative for values of \(x\) in the interval 37 | \{ans_rule(20)\}. 38 | END_TEXT 39 | Context()->normalStrings; 40 | 41 | ########################################################### 42 | # 43 | # The answer 44 | # 45 | 46 | ANS($I->cmp); 47 | 48 | ########################################################### 49 | 50 | ENDDOCUMENT(); # This should be the last executable line in the problem. 51 | -------------------------------------------------------------------------------- /doc/MathObjects/problems/sample12.pg: -------------------------------------------------------------------------------- 1 | ########################################################## 2 | # 3 | # Example showing how to use the built-in answer checker 4 | # for Union MathObjects. 5 | # 6 | 7 | DOCUMENT(); # This should be the first executable line in the problem. 8 | 9 | loadMacros( 10 | "PGstandard.pl", 11 | "MathObjects.pl", 12 | "parserUtils.pl", 13 | ); 14 | 15 | TEXT(beginproblem()); 16 | 17 | ########################################################## 18 | # 19 | # The setup 20 | # 21 | 22 | Context("Interval"); 23 | 24 | $a = non_zero_random(-5,5,1); 25 | $f = Formula("(x^2+1)/(x-$a)")->reduce; 26 | $R = Union("(-inf,$a) U ($a,inf)"); 27 | 28 | ########################################################## 29 | # 30 | # The problem text 31 | # 32 | 33 | Context()->texStrings; 34 | BEGIN_TEXT 35 | 36 | Suppose \(\displaystyle f(x) = $f\). 37 | $PAR 38 | Then \(f\) is defined on the region \{ans_rule(30)\}. 39 | $PAR 40 | ${BCENTER} 41 | ${BSMALL} 42 | Several intervals can be combined using the 43 | set union symbol, ${LQ}${BTT}U${ETT}${RQ}.$BR 44 | Use ${LQ}${BTT}infinity${ETT}${RQ} for ${LQ}\(\infty\)${RQ} and 45 | ${LQ}${BTT}-infinity${ETT}${RQ} for ${LQ}\(-\infty\)${RQ}. 46 | ${ESMALL} 47 | ${ECENTER} 48 | 49 | END_TEXT 50 | Context()->normalStrings; 51 | 52 | ########################################################### 53 | # 54 | # The answer 55 | # 56 | 57 | ANS($R->cmp); 58 | $showPartialCorrectAnswers=1; 59 | 60 | ########################################################### 61 | 62 | ENDDOCUMENT(); # This should be the last executable line in the problem. 63 | -------------------------------------------------------------------------------- /doc/MathObjects/problems/sample13.pg: -------------------------------------------------------------------------------- 1 | ########################################################## 2 | # 3 | # Example showing how to use the built-in answer checker 4 | # for Lists of Interval MathObjects. 5 | # 6 | 7 | DOCUMENT(); # This should be the first executable line in the problem. 8 | 9 | loadMacros( 10 | "PGstandard.pl", 11 | "MathObjects.pl", 12 | "parserUtils.pl", 13 | ); 14 | 15 | TEXT(beginproblem()); 16 | 17 | ########################################################## 18 | # 19 | # The setup 20 | # 21 | 22 | Context("Interval"); 23 | 24 | $a = non_zero_random(-5,5,1); 25 | $f = Formula("(x^2+1)/(x-$a)")->reduce; 26 | $R = Compute("(-inf,$a),($a,inf)"); 27 | 28 | ########################################################## 29 | # 30 | # The problem text 31 | # 32 | 33 | Context()->texStrings; 34 | BEGIN_TEXT 35 | 36 | Suppose \(\displaystyle f(x) = $f\). 37 | $PAR 38 | Then \(f\) is defined on the intervals \{ans_rule(30)\}. 39 | $PAR 40 | ${BCENTER} 41 | ${BSMALL} 42 | To enter more than one interval, separate them by commas.$BR 43 | Use ${LQ}${BTT}infinity${ETT}${RQ} for ${LQ}\(\infty\)${RQ} and 44 | ${LQ}${BTT}-infinity${ETT}${RQ} for ${LQ}\(-\infty\)${RQ}. 45 | ${ESMALL} 46 | ${ECENTER} 47 | 48 | END_TEXT 49 | Context()->normalStrings; 50 | 51 | ########################################################### 52 | # 53 | # The answer 54 | # 55 | 56 | ANS($R->cmp); 57 | $showPartialCorrectAnswers = 1; 58 | 59 | ########################################################### 60 | 61 | ENDDOCUMENT(); # This should be the last executable line in the problem. 62 | -------------------------------------------------------------------------------- /doc/MathObjects/problems/sample14.pg: -------------------------------------------------------------------------------- 1 | ########################################################## 2 | # 3 | # Example showing how to use the built-in answer checker 4 | # for List MathObjects. 5 | # 6 | 7 | DOCUMENT(); # This should be the first executable line in the problem. 8 | 9 | loadMacros( 10 | "PGstandard.pl", 11 | "MathObjects.pl", 12 | "parserUtils.pl", 13 | ); 14 | 15 | TEXT(beginproblem()); 16 | 17 | ########################################################## 18 | # 19 | # The setup 20 | # 21 | 22 | Context("Numeric"); 23 | 24 | $a = random(1,5,1); 25 | $f = Formula("(x^2+1)/(x^2-$a^2)")->reduce; 26 | 27 | ########################################################## 28 | # 29 | # The problem text 30 | # 31 | 32 | Context()->texStrings; 33 | BEGIN_TEXT 34 | 35 | Suppose \(\displaystyle f(x) = $f\). 36 | $PAR 37 | Then \(f\) is defined for all \(x\) except for \{ans_rule(30)\}. 38 | $PAR 39 | ${BCENTER} 40 | ${BSMALL} 41 | To enter more than one value, separate them by commas.$BR 42 | Enter ${LQ}${BTT}NONE${ETT}${RQ} if there are no such values. 43 | ${ESMALL} 44 | ${ECENTER} 45 | 46 | END_TEXT 47 | Context()->normalStrings; 48 | 49 | ########################################################### 50 | # 51 | # The answer 52 | # 53 | 54 | ANS(List($a,-$a)->cmp); 55 | $showPartialCorrectAnswers = 1; 56 | 57 | ########################################################### 58 | 59 | ENDDOCUMENT(); # This should be the last executable line in the problem. 60 | -------------------------------------------------------------------------------- /doc/MathObjects/problems/sample15.pg: -------------------------------------------------------------------------------- 1 | ########################################################## 2 | # 3 | # Example showing how to use the built-in answer checker 4 | # for String MathObjects. 5 | # 6 | 7 | DOCUMENT(); # This should be the first executable line in the problem. 8 | 9 | loadMacros( 10 | "PGstandard.pl", 11 | "MathObjects.pl", 12 | "parserUtils.pl", 13 | ); 14 | 15 | TEXT(beginproblem()); 16 | 17 | ########################################################## 18 | # 19 | # The setup 20 | # 21 | 22 | Context("Numeric"); 23 | 24 | $a = random(1,5,1); 25 | $f = Formula("(x^2-$a)/(x^2+$a)"); 26 | 27 | ########################################################## 28 | # 29 | # The problem text 30 | # 31 | 32 | Context()->texStrings; 33 | BEGIN_TEXT 34 | 35 | Suppose \(\displaystyle f(x) = $f\). 36 | $PAR 37 | Then \(f\) is defined for all \(x\) except for \{ans_rule(30)\}. 38 | $PAR 39 | ${BCENTER} 40 | ${BSMALL} 41 | To enter more than one value, separate them by commas.$BR 42 | Enter ${LQ}${BTT}NONE${ETT}${RQ} if there are no such values. 43 | ${ESMALL} 44 | ${ECENTER} 45 | 46 | END_TEXT 47 | Context()->normalStrings; 48 | 49 | ########################################################### 50 | # 51 | # The answer 52 | # 53 | 54 | ANS(List("NONE")->cmp); # Use List to get proper error messages for a list 55 | # The string "NONE" is predefined 56 | $showPartialCorrectAnswers = 1; 57 | 58 | ########################################################### 59 | 60 | ENDDOCUMENT(); # This should be the last executable line in the problem. 61 | -------------------------------------------------------------------------------- /doc/MathObjects/problems/sample16.pg: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | # 3 | # Example showing how to use the MathObject function 4 | # answer checker. 5 | # 6 | 7 | DOCUMENT(); # This should be the first executable line in the problem. 8 | 9 | loadMacros( 10 | "PGstandard.pl", 11 | "MathObjects.pl", 12 | ); 13 | 14 | TEXT(beginproblem()); 15 | 16 | ########################################################### 17 | # 18 | # The setup 19 | # 20 | Context('Numeric'); 21 | $x = Formula('x'); # used to construct formulas below. 22 | 23 | # 24 | # Define a function and its derivative and make them pretty 25 | # 26 | $a = random(1,8,1); 27 | $b = random(-8,8,1); 28 | $c = random(-8,8,1); 29 | 30 | $f = ($a*$x**2 + $b*$x + $c) -> reduce; 31 | $df = $f->D('x'); 32 | 33 | $x = random(-8,8,1); 34 | 35 | ########################################################### 36 | # 37 | # The problem text 38 | # 39 | 40 | Context()->texStrings; 41 | BEGIN_TEXT 42 | 43 | Suppose \(f(x) = $f\). 44 | $PAR 45 | Then \(f'(x)=\) \{ans_rule(20)\},$BR 46 | and \(f'($x)=\) \{ans_rule(20)\}. 47 | 48 | END_TEXT 49 | Context()->normalStrings; 50 | 51 | ########################################################### 52 | # 53 | # The answers 54 | # 55 | ANS($df->cmp); 56 | ANS($df->eval(x=>$x)->cmp); 57 | $showPartialCorrectAnswers = 1; 58 | 59 | ########################################################### 60 | 61 | ENDDOCUMENT(); # This should be the last executable line in the problem. 62 | -------------------------------------------------------------------------------- /doc/MathObjects/problems/sample17.pg: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | # 3 | # Example showing how to use the MathObject function 4 | # answer checker. 5 | # 6 | 7 | DOCUMENT(); # This should be the first executable line in the problem. 8 | 9 | loadMacros( 10 | "PGstandard.pl", 11 | "MathObjects.pl", 12 | ); 13 | 14 | TEXT(beginproblem()); 15 | 16 | ########################################################### 17 | # 18 | # The setup 19 | # 20 | Context('Numeric')->variables->add(y=>'Real'); 21 | $x = Formula('x'); # used to construct formulas below. 22 | $y = Formula('y'); 23 | 24 | # 25 | # Define a function and its derivative and make them pretty 26 | # 27 | $a = random(1,8,1); 28 | $b = random(-8,8,1); 29 | $c = random(-8,8,1); 30 | 31 | $f = ($a*$x**2 + $b*$x*$y + $c*$y**2) -> reduce; 32 | $fx = $f->D('x'); 33 | $fy = $f->D('y'); 34 | 35 | ########################################################### 36 | # 37 | # The problem text 38 | # 39 | 40 | Context()->texStrings; 41 | BEGIN_TEXT 42 | 43 | Suppose \(f(x,y) = $f\). 44 | $PAR 45 | Then \(f_x(x,y) =\) \{ans_rule(30)\},$BR 46 | and \(f_y(x,y) =\) \{ans_rule(30)\}. 47 | 48 | 49 | END_TEXT 50 | Context()->normalStrings; 51 | 52 | ########################################################### 53 | # 54 | # The answers 55 | # 56 | ANS($fx->cmp); 57 | ANS($fy->cmp); 58 | $showPartialCorrectAnswers = 1; 59 | 60 | ########################################################### 61 | 62 | ENDDOCUMENT(); # This should be the last executable line in the problem. 63 | -------------------------------------------------------------------------------- /doc/MathObjects/problems/sample18.pg: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | # 3 | # Example showing how to use the MathObject function 4 | # answer checker for vector values. 5 | # 6 | 7 | DOCUMENT(); # This should be the first executable line in the problem. 8 | 9 | loadMacros( 10 | "PGstandard.pl", 11 | "MathObjects.pl", 12 | ); 13 | 14 | TEXT(beginproblem()); 15 | 16 | ########################################################### 17 | # 18 | # The setup 19 | # 20 | Context('Vector')->variables->are(t=>'Real'); 21 | 22 | # 23 | # Define a function and its derivative and make them pretty 24 | # 25 | $a = random(1,8,1); 26 | $b = random(-8,8,1); 27 | $c = random(-8,8,1); 28 | 29 | $f = Formula("") -> reduce; 30 | $df = $f->D('t'); 31 | 32 | $t = random(-5,5,1); 33 | 34 | ########################################################### 35 | # 36 | # The problem text 37 | # 38 | 39 | Context()->texStrings; 40 | BEGIN_TEXT 41 | 42 | Suppose \(f(t) = $f\). 43 | $PAR 44 | Then \(f'(t) =\) \{ans_rule(20)\},$BR 45 | and \(f'($t) =\) \{ans_rule(20)\}. 46 | 47 | 48 | END_TEXT 49 | Context()->normalStrings; 50 | 51 | ########################################################### 52 | # 53 | # The answers 54 | # 55 | ANS($df->cmp); 56 | ANS($df->eval(t=>$t)->cmp); 57 | $showPartialCorrectAnswers = 1; 58 | 59 | ########################################################### 60 | 61 | ENDDOCUMENT(); # This should be the last executable line in the problem. 62 | -------------------------------------------------------------------------------- /doc/MathObjects/problems/sample20.pg: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | # 3 | # Example showing how to use the MathObject function 4 | # answer checker, with multi-variable functions, and 5 | # function evaluation and subsitution. 6 | # 7 | 8 | DOCUMENT(); # This should be the first executable line in the problem. 9 | 10 | loadMacros( 11 | "PGstandard.pl", 12 | "MathObjects.pl", 13 | ); 14 | 15 | TEXT(beginproblem()); 16 | 17 | ########################################################### 18 | # 19 | # The setup 20 | # 21 | Context('Numeric')->variables->are( 22 | x=>'Real',y=>'Real', 23 | s=>'Real',t=>'Real' 24 | ); 25 | $x = Formula('x'); $y = Formula('y'); 26 | 27 | $a = random(1,5,1); 28 | $b = random(-5,5,1); 29 | $c = random(-5,5,1); 30 | 31 | $f = ($a*$x**2 + $b*$x*$y + $c*$y**2) -> reduce; 32 | 33 | $x = random(-5,5,1); 34 | $y = random(-5,5,1); 35 | 36 | ########################################################### 37 | # 38 | # The problem text 39 | # 40 | 41 | Context()->texStrings; 42 | BEGIN_TEXT 43 | 44 | Suppose \(f(x) = $f\). 45 | $PAR 46 | Then \(f($x,$y)\) = \{ans_rule(20)\},$BR 47 | and \(f(s+t,s-t)\) = \{ans_rule(30)\}. 48 | 49 | END_TEXT 50 | Context()->normalStrings; 51 | 52 | ########################################################### 53 | # 54 | # The answers 55 | # 56 | ANS($f->eval(x=>$x,y=>$y)->cmp); 57 | ANS($f->substitute(x=>'s+t',y=>'s-t')->cmp); 58 | $showPartialCorrectAnswers = 1; 59 | 60 | ########################################################### 61 | 62 | ENDDOCUMENT(); # This should be the last executable line in the problem. 63 | -------------------------------------------------------------------------------- /doc/MathObjects/problems/sample21.pg: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | # 3 | # Example showing how to use the MathObject answer checker 4 | # for a list of points with formulas as coordinates. 5 | # 6 | 7 | DOCUMENT(); # This should be the first executable line in the problem. 8 | 9 | loadMacros( 10 | "PGstandard.pl", 11 | "MathObects.pl", 12 | "parserUtils.pl", 13 | ); 14 | 15 | TEXT(beginproblem()); 16 | 17 | ########################################################### 18 | # 19 | # The setup 20 | # 21 | Context('Vector')->variables->are(x=>'Real',y=>'Real'); 22 | $x = Formula('x'); $y = Formula('y'); 23 | 24 | $a = random(1,16,1); 25 | $b = non_zero_random(-5,5,1); 26 | 27 | $f = ($x**2 + $a*$y**2 + $b*$x**2*$y) -> reduce; 28 | 29 | $x = sqrt(2*$a)/$b; $y = -1/$b; 30 | 31 | ########################################################### 32 | # 33 | # The problem text 34 | # 35 | 36 | Context()->texStrings; 37 | BEGIN_TEXT 38 | 39 | Suppose \(f(x,y) = $f\). 40 | $PAR 41 | Then \(f\) has critical points at the following 42 | points: \{ans_rule(30)\}. 43 | $PAR 44 | ${BCENTER} 45 | ${BSMALL} 46 | To enter more than one point, separate them by commas.$BR 47 | Enter ${LQ}${BTT}NONE${ETT}${RQ} if there are none. 48 | ${ESMALL} 49 | ${ECENTER} 50 | 51 | END_TEXT 52 | Context()->normalStrings; 53 | 54 | ########################################################### 55 | # 56 | # The answers 57 | # 58 | ANS(List(Point(0,0),Point($x,$y),Point(-$x,$y))->cmp); 59 | $showPartialCorrectAnswers = 1; 60 | 61 | ########################################################### 62 | 63 | ENDDOCUMENT(); # This should be the last executable line in the problem. 64 | -------------------------------------------------------------------------------- /doc/MathObjects/problems/sample22.pg: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | # 3 | # Example showing how to add constants to the context 4 | # and use them in answers. 5 | # 6 | 7 | DOCUMENT(); # This should be the first executable line in the problem. 8 | 9 | loadMacros( 10 | "PGbasicmacros.pl", 11 | "PGanswermacros.pl", 12 | "Parser.pl", 13 | "parserUtils.pl", 14 | ); 15 | 16 | TEXT(beginproblem()); 17 | 18 | ########################################################### 19 | # 20 | # The setup 21 | # 22 | $context = Context('Vector'); 23 | $context->variables->are(t=>'Real'); 24 | $context->constants->add( 25 | p0 => Point(pi,sqrt(2),3/exp(1)), # unlikely to be guessed by student 26 | v => Vector(exp(1),log(10),-(pi**2)), # unlikely to be guessed by student 27 | ); 28 | $context->constants->set(v => {TeX => '\boldsymbol{v}'}); # make it print nicer 29 | 30 | $L = Formula("p0+tv"); 31 | $v = Formula('v'); 32 | 33 | ########################################################### 34 | # 35 | # The problem text 36 | # 37 | 38 | Context()->texStrings; 39 | BEGIN_TEXT 40 | 41 | Suppose \(p_0\) is a point and \($v\) a vector in \(n\)-space. 42 | $PAR 43 | Then the vector-parametric form for the line through \(p_0\) in the 44 | direction of \(v\) is$PAR 45 | ${BBLOCKQUOTE} 46 | \(L(t)\) = \{ans_rule(30)\}. 47 | ${EBLOCKQUOTE} 48 | 49 | END_TEXT 50 | Context()->normalStrings; 51 | 52 | ########################################################### 53 | # 54 | # The answers 55 | # 56 | ANS($L->cmp); 57 | 58 | ########################################################### 59 | 60 | ENDDOCUMENT(); # This should be the last executable line in the problem. 61 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | # Docker Instructions 2 | 3 | These are instructions to build and use a docker image for running the unit tests in `/t`. 4 | 5 | Note: You may need sudo privileges in order to run the `docker` commands. 6 | 7 | ## Building the Docker Image 8 | 9 | Execute the following command from the directory containing your `pg` clone from GitHub. 10 | 11 | ```bash 12 | docker build -t pg -f docker/pg.Dockerfile . 13 | ``` 14 | 15 | ### Running the Test Suite 16 | 17 | To run the tests execute the following command. 18 | 19 | ```bash 20 | docker run -it --rm -v `pwd`:/opt/webwork/pg pg prove -r t 21 | ``` 22 | 23 | ### Using the Shell 24 | 25 | You can also open a `bash` shell in the Docker container via 26 | 27 | ```bash 28 | docker run -it --rm -v `pwd`:/opt/webwork/pg pg 29 | ``` 30 | 31 | At the prompt, just run the commands `prove -r t` as above. 32 | -------------------------------------------------------------------------------- /docker/pg.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | ENV DEBCONF_NONINTERACTIVE_SEEN true 5 | ENV DEBCONF_NOWARNINGS yes 6 | 7 | # Install dependencies 8 | RUN apt-get update \ 9 | && apt-get install -y --no-install-recommends --no-install-suggests \ 10 | cpanminus \ 11 | dvipng \ 12 | dvisvgm \ 13 | imagemagick \ 14 | libc6-dev \ 15 | libclass-accessor-perl \ 16 | libclass-tiny-perl \ 17 | libdbi-perl \ 18 | libencode-perl \ 19 | libgd-perl \ 20 | libhtml-parser-perl \ 21 | libjson-perl \ 22 | libjson-xs-perl \ 23 | liblocale-maketext-lexicon-perl \ 24 | libmojolicious-perl \ 25 | libtest2-suite-perl \ 26 | libtie-ixhash-perl \ 27 | libuuid-tiny-perl \ 28 | libyaml-libyaml-perl \ 29 | make \ 30 | pdf2svg \ 31 | texlive \ 32 | texlive-latex-extra \ 33 | texlive-latex-recommended \ 34 | texlive-plain-generic \ 35 | && apt-get clean \ 36 | && cpanm -fi --notest HTML::TagParser \ 37 | && rm -fr /var/lib/apt/lists/* ./cpanm /root/.cpanm /tmp/* 38 | 39 | RUN mkdir -p /opt/webwork 40 | 41 | WORKDIR /opt/webwork/pg 42 | 43 | ENV PG_ROOT /opt/webwork/pg 44 | 45 | CMD ["/bin/bash"] 46 | -------------------------------------------------------------------------------- /htdocs/helpFiles/Entering-Essays.html: -------------------------------------------------------------------------------- 1 |

Entering Essays

2 | 3 |

4 | In an essay answer text box, you can type a long answer. After after you hit submit, it will be saved so that your 5 | instructor can read it and assign a score at a later time. 6 |

7 |

If your instructor makes any comments on your answer, those comments will appear on the exercise page.

8 |

9 | You can use the MathQuill equation editor to make your math content look pretty. Once you have constructed a math 10 | expression, click the Insert button. In the answer text box, your math content will look like code, but the Preview 11 | button will show you what your isntructor will see. 12 |

13 | -------------------------------------------------------------------------------- /htdocs/helpFiles/Entering-Matrices.html: -------------------------------------------------------------------------------- 1 |

Entering Matrices

2 | 3 |

When there is one big answer box

4 | 24 |

When there are multiple small answer boxes

25 | 31 | -------------------------------------------------------------------------------- /htdocs/helpFiles/Entering-Points.html: -------------------------------------------------------------------------------- 1 |

Entering Points

2 | 3 | 45 | -------------------------------------------------------------------------------- /htdocs/js/DragNDrop/dragndrop.scss: -------------------------------------------------------------------------------- 1 | .dd-bucket-pool { 2 | .dd-pool-bucket-container { 3 | display: flex; 4 | flex-direction: row; 5 | flex-wrap: wrap; 6 | justify-content: space-evenly; 7 | gap: 1rem; 8 | margin-bottom: 1rem; 9 | 10 | .dd-bucket { 11 | display: flex; 12 | flex-direction: column; 13 | width: 350px; 14 | padding: 0.5rem; 15 | color: #000000; 16 | border: 1px solid #388e8e; 17 | border-radius: 5px; 18 | text-align: center; 19 | 20 | .dd-bucket-label { 21 | margin: 0 0 10px 0; 22 | } 23 | 24 | .dd-list { 25 | display: flex; 26 | flex-direction: column; 27 | flex-grow: 1; 28 | gap: 0.5rem; 29 | min-height: 30px; 30 | height: 100%; 31 | 32 | &:empty { 33 | border: 1px dashed #bbb; 34 | border-radius: 5px; 35 | background-color: #e5e5e5; 36 | } 37 | 38 | .dd-item { 39 | display: block; 40 | margin: 0; 41 | padding: 5px 10px; 42 | text-decoration: none; 43 | min-height: 30px; 44 | box-sizing: border-box; 45 | border-radius: 5px; 46 | background: #f5f5f5; 47 | border: 1px solid #388e8e; 48 | text-align: center; 49 | height: auto; 50 | 51 | &:hover { 52 | cursor: pointer; 53 | background: #eee3ce; 54 | color: #222; 55 | } 56 | } 57 | } 58 | } 59 | } 60 | 61 | .dd-buttons { 62 | display: flex; 63 | align-items: center; 64 | justify-content: center; 65 | gap: 1rem; 66 | } 67 | 68 | .dd-remove-bucket-button { 69 | margin-top: 0.5rem; 70 | align-self: center; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /htdocs/js/GraphTool/images/CircleTool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /htdocs/js/GraphTool/images/CubicTool.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /htdocs/js/GraphTool/images/DashTool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /htdocs/js/GraphTool/images/ExcludePointParenthesisTool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ( 5 | 6 | 7 | -------------------------------------------------------------------------------- /htdocs/js/GraphTool/images/ExcludePointTool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /htdocs/js/GraphTool/images/FillTool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /htdocs/js/GraphTool/images/HorizontalParabolaTool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /htdocs/js/GraphTool/images/IncludePointBracketTool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [ 5 | 6 | 7 | -------------------------------------------------------------------------------- /htdocs/js/GraphTool/images/IncludePointTool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /htdocs/js/GraphTool/images/IntervalBracketTool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | [ 6 | ) 7 | 8 | 9 | -------------------------------------------------------------------------------- /htdocs/js/GraphTool/images/IntervalTool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /htdocs/js/GraphTool/images/LineTool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /htdocs/js/GraphTool/images/PointTool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /htdocs/js/GraphTool/images/QuadraticTool.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /htdocs/js/GraphTool/images/SelectTool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /htdocs/js/GraphTool/images/SolidTool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /htdocs/js/GraphTool/images/VerticalParabolaTool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /htdocs/js/ImageView/imageview.scss: -------------------------------------------------------------------------------- 1 | .image-view-elt { 2 | max-width: 100%; 3 | 4 | &:hover { 5 | cursor: pointer; 6 | } 7 | 8 | &.top { 9 | vertical-align: text-top; 10 | } 11 | 12 | &.middle { 13 | vertical-align: middle; 14 | } 15 | 16 | &.bottom { 17 | vertical-align: baseline; 18 | } 19 | } 20 | 21 | .image-view-dialog { 22 | &.modal { 23 | padding: 0 !important; 24 | } 25 | 26 | .modal-dialog { 27 | margin: 0; 28 | } 29 | 30 | .modal-body { 31 | overflow: unset; 32 | padding: 8px; 33 | text-align: center; 34 | box-sizing: content-box !important; 35 | 36 | img { 37 | max-width: 100%; 38 | height: 100%; 39 | } 40 | 41 | svg { 42 | overflow: visible; 43 | } 44 | } 45 | 46 | .modal-header { 47 | padding: 0.1rem 0.5rem; 48 | 49 | .drag-handle { 50 | cursor: pointer; 51 | width: 100%; 52 | height: 26px; 53 | touch-action: none; 54 | } 55 | 56 | .btn { 57 | padding: 0 0.2rem; 58 | margin: 0 0.25rem 0 0; 59 | border: none; 60 | } 61 | 62 | .btn-close { 63 | padding: 0.25rem 0.25rem; 64 | margin: -0.5rem 0 -0.5rem auto; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /htdocs/js/Knowls/knowl.scss: -------------------------------------------------------------------------------- 1 | .knowl { 2 | color: #00a; 3 | background-color: #eef; 4 | border: 1px solid #88f; 5 | border-radius: 3px; 6 | cursor: pointer; 7 | 8 | &:hover { 9 | color: #006; 10 | background-color: #ccf; 11 | border-color: #33f; 12 | } 13 | 14 | &:focus-visible { 15 | border-color: #33f; 16 | box-shadow: 0px 0px 0px 0.2rem #5555ff88; 17 | outline: 0; 18 | } 19 | } 20 | 21 | li > .knowl { 22 | margin: 0.2rem 0; 23 | } 24 | 25 | .knowl-error { 26 | color: darkred; 27 | } 28 | 29 | .knowl-footer { 30 | font-size: x-small; 31 | background: #eef; 32 | color: #555; 33 | } 34 | 35 | // MathJax sets the z-index to 200 which is far below a modal dialog at 1055. So raise that above the modal dialog. 36 | // This is really a bug in MathJax. MathJax should handle this differently. 37 | .CtxtMenu_MenuFrame { 38 | z-index: 1060 !important; 39 | 40 | .CtxtMenu_Menu { 41 | z-index: 1060; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /htdocs/js/Problem/details-accordion.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | const setupAccordion = (accordion) => { 3 | const collapseEl = accordion.querySelector('.collapse'); 4 | const button = accordion.querySelector('summary.accordion-button'); 5 | const details = accordion.querySelector('details.accordion-item'); 6 | if (!collapseEl || !button || !details) return; 7 | 8 | const collapse = new bootstrap.Collapse(collapseEl, { toggle: false }); 9 | button.addEventListener('click', (e) => { 10 | collapse.toggle(); 11 | e.preventDefault(); 12 | }); 13 | 14 | collapseEl.addEventListener('show.bs.collapse', () => { 15 | details.open = true; 16 | button.classList.remove('collapsed'); 17 | }); 18 | collapseEl.addEventListener('hide.bs.collapse', () => button.classList.add('collapsed')); 19 | collapseEl.addEventListener('hidden.bs.collapse', () => (details.open = false)); 20 | }; 21 | 22 | // Deal with solution/hint details that are already on the page. 23 | document.querySelectorAll('.solution.accordion, .hint.accordion').forEach(setupAccordion); 24 | 25 | // Deal with solution/hint details that are added to the page later. 26 | const observer = new MutationObserver((mutationsList) => { 27 | mutationsList.forEach((mutation) => { 28 | mutation.addedNodes.forEach((node) => { 29 | if (node instanceof Element) { 30 | if ( 31 | (node.classList.contains('solution') || node.classList.contains('hint')) && 32 | node.classList.contains('accordion') 33 | ) 34 | setupAccordion(node); 35 | else node.querySelectorAll('.solution.accordion, .hint.accordion').forEach(setupAccordion); 36 | } 37 | }); 38 | }); 39 | }); 40 | observer.observe(document.body, { childList: true, subtree: true }); 41 | })(); 42 | -------------------------------------------------------------------------------- /htdocs/js/Scaffold/scaffold.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | const attachListeners = (node) => { 3 | node.querySelectorAll('.collapse').forEach((section) => { 4 | section.addEventListener('shown.bs.collapse', () => { 5 | // Reflow MathQuill answer boxes so that their contents are rendered correctly 6 | if (window.answerQuills) { 7 | Object.keys(answerQuills).forEach((quill) => { 8 | if (section.querySelector('#' + quill)) answerQuills[quill].mathField.reflow(); 9 | }); 10 | } 11 | }); 12 | 13 | section.addEventListener('hide.bs.collapse', () => { 14 | // Close any open feedback popovers in this scaffold. 15 | for (const button of section.querySelectorAll('.ww-feedback-btn')) { 16 | bootstrap.Popover.getInstance(button)?.hide(); 17 | } 18 | }); 19 | }); 20 | }; 21 | 22 | // Set up any scaffolds already on the page. 23 | document.querySelectorAll('.section-div').forEach(attachListeners); 24 | 25 | // Observer that sets up scaffolds. 26 | const observer = new MutationObserver((mutationsList) => { 27 | mutationsList.forEach((mutation) => { 28 | mutation.addedNodes.forEach((node) => { 29 | if (node instanceof Element) { 30 | if (node.classList.contains('section-div')) attachListeners(node); 31 | else node.querySelectorAll('.section-div').forEach(attachListeners); 32 | } 33 | }); 34 | }); 35 | }); 36 | observer.observe(document.body, { childList: true, subtree: true }); 37 | 38 | // Stop the mutation observer when the window is closed. 39 | window.addEventListener('unload', () => observer.disconnect()); 40 | })(); 41 | -------------------------------------------------------------------------------- /htdocs/js/Scaffold/scaffold.scss: -------------------------------------------------------------------------------- 1 | .section-div { 2 | & > .accordion-header { 3 | & > button.accordion-button { 4 | color: #212121; 5 | padding: 0 1.25rem 0 0; 6 | border: 1px solid #aaa; 7 | box-shadow: none; 8 | 9 | span.section-title { 10 | padding-top: 2px; 11 | } 12 | 13 | span.section-number { 14 | padding: 0 0.25rem; 15 | min-width: 1.25rem; 16 | font-weight: bold; 17 | } 18 | } 19 | 20 | &.cannotopen > button.accordion-button::after { 21 | display: none; 22 | } 23 | 24 | &.canopen > button.accordion-button { 25 | background: lightblue; 26 | } 27 | 28 | &.iscorrect > button.accordion-button { 29 | background: lightgreen; 30 | } 31 | 32 | &.cannotopen { 33 | & > button.accordion-button { 34 | background: #eee; 35 | 36 | &:focus { 37 | box-shadow: none; 38 | } 39 | 40 | &:hover { 41 | cursor: default; 42 | } 43 | } 44 | } 45 | } 46 | 47 | .accordion-header.canopen button.accordion-button:focus { 48 | box-shadow: inset 0 0 3px 2px #00c; 49 | } 50 | 51 | & > div.accordion-collapse { 52 | background: #fafafa; 53 | border-top: 1px solid rgba(0, 0, 0, 0.125); 54 | } 55 | } 56 | 57 | // The above css is reversed for rtl css files by rtlcss. So set things back to 'ltr' mode 58 | // in problems that have 'ltr' set for the rtl css file. 59 | /* rtl:raw: 60 | .problem-content[dir="ltr"] .section-div > .accordion-header > button.accordion-button { 61 | padding: 0 1.25rem 0 0; 62 | } 63 | 64 | .problem-content[dir="ltr"] .section-div > .accordion-header > button.accordion-button::after { 65 | margin-right: unset !important; 66 | margin-left: auto; 67 | } 68 | */ 69 | -------------------------------------------------------------------------------- /htdocs/js/UnionTables/union-tables.scss: -------------------------------------------------------------------------------- 1 | .union-table { 2 | &.union-table-centered { 3 | margin-left: auto; 4 | margin-right: auto; 5 | } 6 | 7 | @each $name, $thickness in ('minor': 1px, 'medium': 2px, 'major': 3px) { 8 | &.union-table-bordered-#{$name} { 9 | & > :not(caption) > *, 10 | & > :not(caption) > * > * { 11 | border-width: $thickness; 12 | } 13 | } 14 | } 15 | 16 | @for $spacing from 1 to 10 { 17 | &.union-table-s#{$spacing} { 18 | border-collapse: separate; 19 | border-spacing: #{$spacing}px; 20 | } 21 | } 22 | 23 | @for $padding from 1 to 20 { 24 | &.union-table-p#{$padding} { 25 | & > :not(caption) > *, 26 | & > :not(caption) > * > * { 27 | padding: #{$padding}px; 28 | } 29 | } 30 | } 31 | 32 | tr.union-table-line { 33 | td { 34 | padding-top: 0; 35 | padding-bottom: 0; 36 | 37 | hr { 38 | border: 1px solid black; 39 | height: 1px; 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /htdocs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pg.javascript_package_manager", 3 | "description": "Third party javascript for PG", 4 | "license": "GPL-2.0+", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/openwebwork/pg" 8 | }, 9 | "scripts": { 10 | "generate-assets": "node generate-assets", 11 | "prepare": "npm run generate-assets", 12 | "prettier-format": "prettier --ignore-path=../.gitignore --write \"**/*.{js,css,scss,html}\" \"../**/*.dist.yml\"", 13 | "prettier-check": "prettier --ignore-path=../.gitignore --check \"**/*.{js,css,scss,html}\" \"../**/*.dist.yml\"" 14 | }, 15 | "dependencies": { 16 | "jsxgraph": "^1.9.2", 17 | "jszip": "^3.10.1", 18 | "jszip-utils": "^0.1.0", 19 | "mathquill": "github:openwebwork/mathquill#WeBWorK-2.19", 20 | "plotly.js-dist-min": "^2.32.0", 21 | "sortablejs": "^1.15.2" 22 | }, 23 | "devDependencies": { 24 | "autoprefixer": "^10.4.19", 25 | "chokidar": "^3.6.0", 26 | "cssnano": "^6.1.2", 27 | "postcss": "^8.4.38", 28 | "prettier": "^3.2.5", 29 | "rtlcss": "^4.1.1", 30 | "sass": "^1.75.0", 31 | "terser": "^5.30.4", 32 | "yargs": "^17.7.2" 33 | }, 34 | "browserslist": [ 35 | "last 10 Chrome versions", 36 | "last 10 Firefox versions", 37 | "last 4 Edge versions", 38 | "last 7 Safari versions", 39 | "last 8 Android versions", 40 | "last 8 ChromeAndroid versions", 41 | "last 8 FirefoxAndroid versions", 42 | "last 10 iOS versions", 43 | "last 5 Opera versions" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /lib/AnswerIO.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | AnswerIO.pm 5 | 6 | =head1 SYNPOSIS 7 | 8 | This is not really an object, but it gives us a place to IO used by answer 9 | macros. 10 | 11 | 12 | 13 | =head1 DESCRIPTION 14 | 15 | 16 | =head2 Examples: 17 | 18 | 19 | 20 | =cut 21 | 22 | package AnswerIO; 23 | 24 | use strict; 25 | 26 | # Code for saving Answers to a file 27 | # function, not a method 28 | # Code in .pm files can access the disk. 29 | 30 | sub saveAnswerToFile { 31 | my $logFileID = shift; 32 | my $string = shift; 33 | # We want to allow acces only to predetermined files 34 | # We accomplish this by translating legal IDs into a file name 35 | 36 | my $rh_allowableFiles = { 37 | preflight => 'preflight.log', 38 | questionnaire => 'questionnaire.txt', 39 | 40 | }; 41 | my $error = undef; 42 | my $logFileName = $rh_allowableFiles->{$logFileID}; 43 | if (defined($logFileName)) { 44 | my $accessLog = Global::getCourseLogsDirectory() . $logFileName; 45 | #$error = "access Log is $accessLog"; 46 | #$error .="string is $string"; 47 | open(LOG, ">>$accessLog") or $error .= "Can't open course access log $accessLog"; 48 | print LOG $string; #no format is forced on data. 49 | close(LOG); 50 | } else { 51 | $error = "Error: The file ID $logFileID is not recognized."; 52 | } 53 | return $error; 54 | } 55 | 56 | 1; 57 | -------------------------------------------------------------------------------- /lib/Complex.pm: -------------------------------------------------------------------------------- 1 | package Complex; 2 | 3 | use strict; 4 | 5 | *i = *Complex1::i; 6 | @Complex::ISA = qw(Complex1); 7 | 8 | 1; 9 | -------------------------------------------------------------------------------- /lib/LimitedPolynomial.pm: -------------------------------------------------------------------------------- 1 | # 2 | # Temporary placeholder file. 3 | # 4 | # The pg/macros/contextLimitedPolynomial.pl file has been recombined into a single file, 5 | # but the update requires an update to webwork2 as well (to remove this file from the 6 | # list of libraries to load automatically). Once people have updated webwork2, 7 | # this file can be removed. It is only here so that pg can be updated separately without 8 | # causing problems. 9 | # 10 | 11 | 1; 12 | -------------------------------------------------------------------------------- /lib/Parser/BOP/add.pm: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # 3 | # Implement addition. 4 | # 5 | package Parser::BOP::add; 6 | use strict; 7 | our @ISA = qw(Parser::BOP); 8 | 9 | # 10 | # Check that the operand types are compatible. 11 | # 12 | sub _check { 13 | my $self = shift; 14 | return if ($self->checkStrings()); 15 | return if ($self->checkLists()); 16 | return if ($self->checkNumbers()); 17 | my ($ltype, $rtype) = $self->promotePoints(); 18 | if (Parser::Item::typeMatch($ltype, $rtype)) { $self->{type} = $ltype } 19 | else { $self->matchError($ltype, $rtype) } 20 | } 21 | 22 | # 23 | # Do addition. 24 | # 25 | sub _eval { $_[1] + $_[2] } 26 | 27 | # 28 | # Remove addition with zero. 29 | # Turn addition of negative into subtraction. 30 | # 31 | sub _reduce { 32 | my $self = shift; 33 | my $equation = $self->{equation}; 34 | my $reduce = $equation->{context}{reduction}; 35 | return $self->{lop} if $self->{rop}{isZero} && $reduce->{'x+0'}; 36 | return $self->{rop} if $self->{lop}{isZero} && $reduce->{'0+x'}; 37 | if ($self->{rop}->isNeg && $reduce->{'x+(-y)'}) { 38 | $self = $self->Item("BOP")->new($equation, '-', $self->{lop}, $self->{rop}{op}); 39 | $self = $self->reduce; 40 | } elsif ($self->{lop}->isNeg && $reduce->{'(-x)+y'}) { 41 | $self = $self->Item("BOP")->new($equation, '-', $self->{rop}, $self->{lop}{op}); 42 | $self = $self->reduce; 43 | } 44 | return $self; 45 | } 46 | 47 | $Parser::reduce->{'0+x'} = 1; 48 | $Parser::reduce->{'x+0'} = 1; 49 | $Parser::reduce->{'x+(-y)'} = 1; 50 | $Parser::reduce->{'(-x)+y'} = 1; 51 | 52 | ######################################################################### 53 | 54 | 1; 55 | -------------------------------------------------------------------------------- /lib/Parser/BOP/comma.pm: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # 3 | # The comma operator 4 | # 5 | package Parser::BOP::comma; 6 | use strict; 7 | our @ISA = qw(Parser::BOP); 8 | 9 | # 10 | # Start forming a list, and set the list type if 11 | # the left and right operands are the same type. 12 | # If the left or right operands are already lists, 13 | # update the number of items in the new list. 14 | # 15 | sub _check { 16 | my $self = shift; 17 | my ($ltype, $rtype) = ($self->{lop}->typeRef, $self->{rop}->typeRef); 18 | my $type = Value::Type('Comma', 2, $Value::Type{unknown}); 19 | if ($ltype->{name} eq 'Comma') { 20 | $type->{length} += $self->{lop}->length - 1; 21 | $ltype = $self->{lop}->entryType; 22 | } 23 | if ($rtype->{name} eq 'Comma') { 24 | $type->{length} += $self->{rop}->length - 1; 25 | $rtype = $self->{rop}->entryType; 26 | } 27 | $type->{entryType} = $ltype if (Parser::Item::typeMatch($ltype, $rtype)); 28 | $self->{type} = $type; 29 | } 30 | 31 | # 32 | # evaluate by forming a list 33 | # 34 | sub _eval { ($_[1], $_[2]) } 35 | 36 | # 37 | # If the operator is listed as a comma, make a list 38 | # out of the lists that are the left and right operands. 39 | # Otherwise return the item itself 40 | # 41 | sub makeList { 42 | my $self = shift; 43 | return $self unless $self->{def}{isComma}; 44 | return ($self->{lop}->makeList, $self->{rop}->makeList); 45 | } 46 | 47 | ######################################################################### 48 | 49 | 1; 50 | -------------------------------------------------------------------------------- /lib/Parser/BOP/undefined.pm: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # 3 | # Use this for undefined operators in the Context operator list. 4 | # They will still be recognized by the parser (so you don't get 5 | # 'unexpected character' errors), but get a message that the operation 6 | # is not defined in this context. 7 | # 8 | 9 | package Parser::BOP::undefined; 10 | use strict; 11 | our @ISA = qw(Parser::BOP); 12 | 13 | sub _check { 14 | my $self = shift; 15 | my $bop = $self->{def}{string} || $self->{bop}; 16 | $self->Error("Can't use '%s' in this context", $bop); 17 | } 18 | 19 | ######################################################################### 20 | 21 | 1; 22 | -------------------------------------------------------------------------------- /lib/Parser/Context/Constants.pm: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # 3 | # Implements the list of named constants. 4 | # 5 | package Parser::Context::Constants; 6 | use strict; 7 | our @ISA = qw(Value::Context::Data); 8 | 9 | sub init { 10 | my $self = shift; 11 | $self->{dataName} = 'constants'; 12 | $self->{name} = 'constant'; 13 | $self->{Name} = 'Constant'; 14 | $self->{namePattern} = qr/\w+/; 15 | $self->{tokenType} = 'const'; 16 | } 17 | 18 | # 19 | # Create/Uncreate data for constants 20 | # 21 | sub create { 22 | my $self = shift; 23 | my $value = shift; 24 | return { value => $value, keepName => 1 } unless ref($value) eq 'HASH'; 25 | $value->{keepName} = 1 unless defined($value->{keepName}); 26 | return $value; 27 | } 28 | sub uncreate { shift; (shift)->{value} } 29 | 30 | # 31 | # Return a constant's value 32 | # 33 | sub value { 34 | my $self = shift; 35 | my $x = shift; 36 | return $self->{context}->constants->resolveDef($x)->{value}; 37 | } 38 | 39 | ######################################################################### 40 | 41 | 1; 42 | -------------------------------------------------------------------------------- /lib/Parser/Context/Operators.pm: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # 3 | # Implements the list of Operators 4 | # 5 | package Parser::Context::Operators; 6 | use strict; 7 | our @ISA = qw(Value::Context::Data); 8 | 9 | sub init { 10 | my $self = shift; 11 | $self->{dataName} = 'operators'; 12 | $self->{name} = 'operator'; 13 | $self->{Name} = 'operator'; 14 | $self->{namePattern} = qr/.+/; 15 | $self->{tokenType} = 'op'; 16 | } 17 | 18 | # 19 | # Remove an operator from the list by assigning it 20 | # the undefined operator. This means it will still 21 | # be recognized by the parser, but will generate an 22 | # error message whenever it is used. 23 | # 24 | sub undefine { 25 | my $self = shift; 26 | my @data = (); 27 | foreach my $x (@_) { 28 | if ($self->{context}{operators}{$x}{type} eq 'unary') { 29 | push( 30 | @data, 31 | $x => { 32 | class => 'Parser::UOP::undefined', 33 | oldClass => $self->get($x)->{class}, 34 | } 35 | ); 36 | } else { 37 | push( 38 | @data, 39 | $x => { 40 | class => 'Parser::BOP::undefined', 41 | oldClass => $self->get($x)->{class}, 42 | } 43 | ); 44 | } 45 | } 46 | $self->set(@data); 47 | } 48 | 49 | sub redefine { 50 | my $self = shift; 51 | my $X = shift; 52 | return $self->SUPER::redefine($X, @_) if scalar(@_) > 0; 53 | $X = [$X] unless ref($X) eq 'ARRAY'; 54 | my @data = (); 55 | foreach my $x (@{$X}) { 56 | my $oldClass = $self->get($x)->{oldClass}; 57 | push(@data, $x => { class => $oldClass, oldClass => undef }) 58 | if $oldClass; 59 | } 60 | $self->set(@data); 61 | } 62 | 63 | ######################################################################### 64 | 65 | 1; 66 | -------------------------------------------------------------------------------- /lib/Parser/Context/Reduction.pm: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # 3 | # Implement the list of Parser::Context::Reduction type 4 | # 5 | package Parser::Context::Reduction; 6 | use strict; 7 | our @ISA = qw(Value::Context::Data); 8 | 9 | sub init { 10 | my $self = shift; 11 | $self->{dataName} = 'reduction'; 12 | $self->{name} = 'reduction'; 13 | $self->{Name} = 'Reduction'; 14 | $self->{namePattern} = qr/\S+/; 15 | $self->{allowAlias} = 0; 16 | } 17 | 18 | sub update { } # no pattern or tokens needed 19 | sub addToken { } 20 | sub removeToken { } 21 | 22 | sub reduce { 23 | my $self = shift; 24 | my %flags; 25 | foreach my $id (@_) { $flags{$id} = 1 } 26 | $self->set(%flags); 27 | } 28 | 29 | sub noreduce { 30 | my $self = shift; 31 | my %flags; 32 | foreach my $id (@_) { $flags{$id} = 0 } 33 | $self->set(%flags); 34 | } 35 | 36 | ######################################################################### 37 | 38 | 1; 39 | -------------------------------------------------------------------------------- /lib/Parser/Function/numeric2.pm: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # 3 | # Implement functions having two real inputs 4 | # 5 | package Parser::Function::numeric2; 6 | use strict; 7 | our @ISA = qw(Parser::Function); 8 | 9 | # 10 | # Check for two real-valued arguments 11 | # 12 | sub _check { 13 | my $self = shift; 14 | return if ($self->checkArgCount(2)); 15 | if ( 16 | ( 17 | $self->{params}->[0]->isNumber 18 | && $self->{params}->[1]->isNumber 19 | && !$self->{params}->[0]->isComplex 20 | && !$self->{params}->[1]->isComplex 21 | ) 22 | || $self->context->flag("allowBadFunctionInputs") 23 | ) 24 | { 25 | $self->{type} = $Value::Type{number}; 26 | } else { 27 | $self->Error("Function '%s' has the wrong type of inputs", $self->{name}); 28 | } 29 | } 30 | 31 | # 32 | # Check that the inputs are OK 33 | # 34 | sub _call { 35 | my $self = shift; 36 | my $name = shift; 37 | Value::Error("Function '%s' has too many inputs", $name) if scalar(@_) > 2; 38 | Value::Error("Function '%s' has too few inputs", $name) if scalar(@_) < 2; 39 | Value::Error("Function '%s' has the wrong type of inputs", $name) 40 | unless Value::matchNumber($_[0]) && Value::matchNumber($_[1]); 41 | return $self->$name(@_); 42 | } 43 | 44 | # 45 | # Call the appropriate routine 46 | # 47 | sub _eval { 48 | my $self = shift; 49 | my $name = $self->{name}; 50 | $self->$name(@_); 51 | } 52 | 53 | # 54 | # Do the core atan2 call 55 | # 56 | sub atan2 { shift; CORE::atan2($_[0], $_[1]) } 57 | 58 | ######################################################################### 59 | 60 | 1; 61 | -------------------------------------------------------------------------------- /lib/Parser/Function/undefined.pm: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # 3 | # Use this for undefined functions in the Context function list. 4 | # They will still be recognized by the parser (so you don't get 5 | # 'undefined variable' errors), but get a message that the function 6 | # is not defined in this context. 7 | # 8 | 9 | package Parser::Function::undefined; 10 | use strict; 11 | our @ISA = qw(Parser::Function); 12 | 13 | sub _check { 14 | my $self = shift; 15 | $self->Error("Function '%s' is not allowed in this context", $self->{name}); 16 | } 17 | 18 | sub _call { 19 | my $self = shift; 20 | my $name = shift; 21 | Value::Error("Function '%s' is not allowed in this context", $name); 22 | } 23 | 24 | ######################################################################### 25 | 26 | 1; 27 | -------------------------------------------------------------------------------- /lib/Parser/Function/vector.pm: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # 3 | # Implement functions on vectors 4 | # 5 | package Parser::Function::vector; 6 | use strict; 7 | our @ISA = qw(Parser::Function); 8 | 9 | # 10 | # Check for a single vector-valued input 11 | # 12 | sub _check { (shift)->checkVector(@_) } 13 | 14 | # 15 | # Evaluate by promoting to a vector 16 | # and then calling the routine from Value.pm 17 | # 18 | sub _eval { 19 | my $self = shift; 20 | my $name = $self->{name}; 21 | my $v = $self->Package("Vector")->promote($self->context, $_[0]); 22 | $v->$name; 23 | } 24 | 25 | # 26 | # Check for a single vector-valued argument 27 | # Then promote it to a vector (does error checking) 28 | # and call the routine from Value.pm 29 | # 30 | sub _call { 31 | my $self = shift; 32 | my $name = shift; 33 | Value::Error("Function '%s' has too many inputs", $name) if scalar(@_) > 1; 34 | Value::Error("Function '%s' has too few inputs", $name) if scalar(@_) == 0; 35 | my $v = shift; 36 | my $context = (Value::isValue($v) ? $v : $self)->context; 37 | $self->Package("Vector")->promote($context, $v)->$name; 38 | } 39 | 40 | ######################################################################### 41 | 42 | 1; 43 | -------------------------------------------------------------------------------- /lib/Parser/Legacy.pm: -------------------------------------------------------------------------------- 1 | # 2 | # Load all the legacy code 3 | # 4 | 5 | use Parser::Legacy::NumberWithUnits; 6 | use Parser::Legacy::LimitedNumeric; 7 | use Parser::Legacy::Numeric; 8 | 9 | use Parser::Legacy::LimitedComplex; 10 | 11 | 1; 12 | -------------------------------------------------------------------------------- /lib/Parser/Legacy/Numeric.pm: -------------------------------------------------------------------------------- 1 | ########################################################## 2 | # 3 | # Implements a context in which the "step" and "fact" 4 | # functions are defined. These were defined in the old 5 | # AlgParser, but are not in the Parser's standard 6 | # Numeric context. 7 | # 8 | # Warning: since step and fact already are defined in 9 | # PGauxiliarymacros.pl we can't redefine them here, so you 10 | # can't use step(formula) or fact(formula) to automatically 11 | # generate Formula objects, as you can with all the other 12 | # functions. Since this context is for compatibility with 13 | # old problems that didn't know about Formula objects 14 | # anyway, that should not be a problem. 15 | # 16 | 17 | package Parser::Legacy::Numeric; 18 | our @ISA = qw(Parser::Function::numeric); 19 | sub step { shift; do_step(shift) } 20 | sub do_step { Value::pgCall('step', @_) } 21 | sub fact { shift; do_fact(shift) } 22 | sub do_fact { Value::pgCall('fact', @_) } 23 | 24 | my $context = $Parser::Context::Default::context{Numeric}->copy; 25 | $Parser::Context::Default::context{LegacyNumeric} = $context; 26 | $context->functions->add( 27 | step => { class => 'Parser::Legacy::Numeric', perl => 'Parser::Legacy::Numeric::do_step' }, 28 | fact => { class => 'Parser::Legacy::Numeric', perl => 'Parser::Legacy::Numeric::do_fact' }, 29 | ); 30 | $context->{name} = "LegacyNumeric"; 31 | 32 | 1; 33 | -------------------------------------------------------------------------------- /lib/Parser/List/AbsoluteValue.pm: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # 3 | # Implements the Absolute Value class 4 | # (it works like a list, since it has "parens" at both ends) 5 | # 6 | package Parser::List::AbsoluteValue; 7 | use strict; 8 | our @ISA = qw(Parser::List); 9 | 10 | # 11 | # Check that only one number is inside the |...| 12 | # 13 | sub _check { 14 | my $self = shift; 15 | $self->{type}{list} = 0; 16 | $self->Error("Only one value allowed within absolute values") 17 | if ($self->{type}{length} != 1); 18 | my $arg = $self->{coords}[0]; 19 | $self->Error("Absolute value can't be taken of %s", $arg->type) 20 | unless $arg->type =~ /Number|Point|Vector/ || $self->context->flag("allowBadOperands"); 21 | $self->{type} = $Value::Type{number}; 22 | } 23 | 24 | sub class {'AbsoluteValue'}; # don't report List 25 | 26 | # 27 | # Compute using abs() 28 | # 29 | sub _eval { abs($_[1][0]) } 30 | 31 | # 32 | # Use abs() in perl mode 33 | # 34 | sub perl { 35 | my $self = shift; 36 | my $parens = shift; 37 | my $perl = 'abs(' . $self->{coords}[0]->perl . ')'; 38 | $perl = '(' . $perl . ')' if $parens; 39 | return $perl; 40 | } 41 | 42 | ######################################################################### 43 | 44 | 1; 45 | -------------------------------------------------------------------------------- /lib/Parser/List/List.pm: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # 3 | # Implements the List class 4 | # 5 | package Parser::List::List; 6 | use strict; 7 | our @ISA = qw(Parser::List); 8 | 9 | # 10 | # The basic List class does it all. We only need this class 11 | # for its name. 12 | # 13 | 14 | # 15 | # Produce a string version with extra space 16 | # 17 | sub string { 18 | my $self = shift; 19 | my $precedence = shift; 20 | my @coords = (); 21 | my $def = $self->context->{lists}{ $self->type }; 22 | my $separator = $def->{separator}; 23 | $separator = ", " unless defined $separator; 24 | foreach my $x (@{ $self->{coords} }) { push(@coords, $x->string) } 25 | return $self->{open} . join($separator, @coords) . $self->{close}; 26 | } 27 | 28 | ######################################################################### 29 | 30 | 1; 31 | -------------------------------------------------------------------------------- /lib/Parser/List/Point.pm: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # 3 | # Implements the Point class 4 | # 5 | package Parser::List::Point; 6 | use strict; 7 | our @ISA = qw(Parser::List); 8 | 9 | # 10 | # The basic List class does most of the checking. 11 | # 12 | 13 | sub _check { 14 | my $self = shift; 15 | return if $self->context->flag("allowBadOperands"); 16 | foreach my $x (@{ $self->{coords} }) { 17 | unless ($x->isNumber) { 18 | my $type = $x->type; 19 | $type = (($type =~ m/^[aeiou]/i) ? "an " : "a ") . $type; 20 | $self->{equation}->Error([ "Coordinates of Points must be Numbers, not %s", $type ]); 21 | } 22 | } 23 | $self->{equation}->Error("Coordinates of a Point must be constant") 24 | if ($self->context->flag("requireConstantPoints") && !($self->{isConstant})); 25 | } 26 | 27 | ######################################################################### 28 | 29 | 1; 30 | -------------------------------------------------------------------------------- /lib/Parser/List/Set.pm: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # 3 | # Implements the Set class 4 | # 5 | package Parser::List::Set; 6 | use strict; 7 | our @ISA = qw(Parser::List); 8 | 9 | # 10 | # Check that the entries are numbers. 11 | # 12 | sub _check { 13 | my $self = shift; 14 | return if $self->context->flag("allowBadOperands"); 15 | foreach my $x (@{ $self->{coords} }) { 16 | $self->Error("Sets can't contain infinity") if $x->{isInfinite}; 17 | $self->Error("Entries in a set must be real numbers") unless $x->isRealNumber; 18 | } 19 | } 20 | 21 | sub canBeInUnion {1} 22 | 23 | ######################################################################### 24 | 25 | 1; 26 | -------------------------------------------------------------------------------- /lib/Parser/UOP/factorial.pm: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # 3 | # Implements factorial 4 | # 5 | package Parser::UOP::factorial; 6 | use strict; 7 | our @ISA = qw(Parser::UOP); 8 | 9 | # 10 | # Check that the operand is a number 11 | # 12 | sub _check { 13 | my $self = shift; 14 | if ($self->{op}->isRealNumber || $self->context->flag("allowBadOperands")) { $self->{type} = $Value::Type{number} } 15 | else { $self->Error("Factorial only works on integers") } 16 | } 17 | 18 | # 19 | # Evaluate the factorial 20 | # 21 | sub _eval { 22 | my $self = shift; 23 | my $n = shift; 24 | my $f = 1; 25 | $self->Error("Factorial can only be taken of (non-negative) integers") 26 | unless $n =~ m/^\d+$/; 27 | return $self->Package("Infinity")->new() if $n > 170; 28 | while ($n > 0) { $f *= $n; $n-- } 29 | return $f; 30 | } 31 | 32 | ######################################################################### 33 | 34 | # 35 | # Create a new formula if the function's arguments are formulas 36 | # Otherwise evaluate the function call. 37 | # 38 | sub call { 39 | my $self = shift; 40 | $self->Error("Factorial requires an argument") if scalar(@_) == 0; 41 | $self->Error("Factorial should have only one argument") unless scalar(@_) == 1; 42 | return $self->_eval(@_) unless Value::isFormula($_[0]); 43 | my $formula = $self->Package("Formula")->blank($self->context); 44 | my @args = Value::toFormula($formula, @_); 45 | $formula->{tree} = $formula->Item("UOP")->new($formula, '!', @args); 46 | return $formula; 47 | } 48 | 49 | sub Error { 50 | my $self = shift; 51 | $self->SUPER::Error(@_) if ref($self); 52 | Value::Error(@_); 53 | } 54 | 55 | ######################################################################### 56 | 57 | 1; 58 | -------------------------------------------------------------------------------- /lib/Parser/UOP/minus.pm: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # 3 | # Implements unary minus 4 | # 5 | package Parser::UOP::minus; 6 | use strict; 7 | our @ISA = qw(Parser::UOP); 8 | 9 | # 10 | # Check that the operand is OK. 11 | # 12 | sub _check { 13 | my $self = shift; 14 | return if ($self->checkInfinite); 15 | return if ($self->checkString); 16 | return if ($self->checkList); 17 | return if ($self->checkNumber); 18 | $self->{type} = { %{ $self->{op}->typeRef } }; 19 | } 20 | 21 | # 22 | # Negate the operand. 23 | # 24 | sub _eval { -($_[1]) } 25 | 26 | # 27 | # Remove double negatives. 28 | # 29 | sub _reduce { 30 | my $self = shift; 31 | my $op = $self->{op}; 32 | my $reduce = $self->{equation}{context}{reduction}; 33 | if ($op->isNeg && $reduce->{'-(-x)'}) { 34 | delete $op->{op}{noParens}; 35 | return $op->{op}; 36 | } 37 | return $op if $op->{isZero} && $reduce->{'-0'}; 38 | return $self; 39 | } 40 | 41 | $Parser::reduce->{'-(-x)'} = 1; 42 | $Parser::reduce->{'-0'} = 1; 43 | 44 | ######################################################################### 45 | 46 | 1; 47 | -------------------------------------------------------------------------------- /lib/Parser/UOP/plus.pm: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # 3 | # Implements unary plus 4 | # 5 | package Parser::UOP::plus; 6 | use strict; 7 | our @ISA = qw(Parser::UOP); 8 | 9 | # 10 | # Check that the operand is OK 11 | # 12 | sub _check { 13 | my $self = shift; 14 | return if ($self->checkInfinite); 15 | return if ($self->checkString); 16 | return if ($self->checkList); 17 | return if ($self->checkNumber); 18 | $self->{type} = { %{ $self->{op}->typeRef } }; 19 | } 20 | 21 | # 22 | # Plus doesn't change the value 23 | # 24 | sub _eval { $_[1] } 25 | 26 | # 27 | # Remove the redundant plus sign 28 | # 29 | sub _reduce { 30 | my $self = shift; 31 | my $reduce = $self->{equation}{context}{reduction}; 32 | return $self->{op} if $reduce->{'+x'}; 33 | return $self; 34 | } 35 | 36 | $Parser::reduce->{'+x'} = 1; 37 | 38 | ######################################################################### 39 | 40 | 1; 41 | -------------------------------------------------------------------------------- /lib/Parser/UOP/undefined.pm: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # 3 | # Use this for undefined operators in the Context operator list. 4 | # They will still be recognized by the parser (so you don't get 5 | # 'unexpected character' errors), but get a message that the operation 6 | # is not defined in this context. 7 | # 8 | 9 | package Parser::UOP::undefined; 10 | use strict; 11 | our @ISA = qw(Parser::UOP); 12 | 13 | sub _check { 14 | my $self = shift; 15 | my $uop = $self->{def}{string} || $self->{uop}; 16 | $self->Error("Can't use '%s' in this context", $uop); 17 | } 18 | 19 | ######################################################################### 20 | 21 | 1; 22 | -------------------------------------------------------------------------------- /lib/Rserve.pm: -------------------------------------------------------------------------------- 1 | package Rserve; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | my $rserve_loaded = eval { 7 | require Statistics::R::IO::Rserve; 8 | 1; 9 | }; 10 | 11 | sub access { 12 | die 'Statistics::R::IO::Rserve could not be loaded. Have you installed the module?' 13 | unless $rserve_loaded; 14 | 15 | Statistics::R::IO::Rserve->new(@_); 16 | } 17 | 18 | ## Evaluates an R expression guarding it inside an R `try` function 19 | ## 20 | ## Returns the result as a REXP if no exceptions were raised, or 21 | ## `die`s with the text of the exception message. 22 | sub try_eval { 23 | my ($rserve, $query) = @_; 24 | 25 | my $result = $rserve->eval("try({ $query }, silent=TRUE)"); 26 | die $result->to_pl->[0] if _inherits($result, 'try-error'); 27 | # die $result->to_pl->[0] if $result->inherits('try-error'); 28 | 29 | $result; 30 | } 31 | 32 | ## Returns a REXP's Perl representation, dereferencing it if it's an 33 | ## array reference 34 | ## 35 | ## `REXP::to_pl` returns a string scalar for Symbol, undef for Null, 36 | ## and an array reference to contents for all vector types. This 37 | ## function is a utility wrapper to make it easy to assign a Vector's 38 | ## representation to an array variable, while still working sensibly 39 | ## for non-arrays. 40 | sub unref_rexp { 41 | my $rexp = shift; 42 | 43 | my $value = $rexp->to_pl; 44 | if (ref($value) eq ref([])) { 45 | @{$value}; 46 | } else { 47 | $value; 48 | } 49 | } 50 | 51 | ## Reimplements method C of class L 52 | ## until I figure out why calling it directly doesn't work in the safe 53 | ## compartment 54 | sub _inherits { 55 | my ($rexp, $class) = @_; 56 | 57 | my $attributes = $rexp->attributes; 58 | return unless $attributes && $attributes->{'class'}; 59 | 60 | grep {/^$class$/} @{ $attributes->{'class'}->to_pl }; 61 | } 62 | 63 | 1; 64 | -------------------------------------------------------------------------------- /lib/Value/Context/Flags.pm: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # 3 | # Implement the list of Value::Flags types 4 | # 5 | package Value::Context::Flags; 6 | use strict; 7 | our @ISA = ("Value::Context::Data"); 8 | 9 | sub init { 10 | my $self = shift; 11 | $self->{dataName} = 'flags'; 12 | $self->{name} = 'flag'; 13 | $self->{Name} = 'Flag'; 14 | $self->{namePattern} = qr/[-\w_.]+/; 15 | $self->{allowAlias} = 0; 16 | } 17 | 18 | sub update { } # no pattern or tokens needed 19 | sub addToken { } 20 | sub removeToken { } 21 | 22 | ######################################################################### 23 | 24 | 1; 25 | -------------------------------------------------------------------------------- /lib/Value/Context/Lists.pm: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # 3 | # Implement the list of known Value::List types 4 | # 5 | package Value::Context::Lists; 6 | use strict; 7 | our @ISA = ("Value::Context::Data"); 8 | 9 | sub init { 10 | my $self = shift; 11 | $self->{dataName} = 'lists'; 12 | $self->{name} = 'list'; 13 | $self->{Name} = 'List'; 14 | $self->{namePattern} = qr/\S+/; 15 | $self->{allowAlias} = 0; 16 | } 17 | 18 | sub update { } # no pattern or tokens needed 19 | sub addToken { } 20 | sub removeToken { } 21 | 22 | ######################################################################### 23 | 24 | 1; 25 | -------------------------------------------------------------------------------- /macros/PGcourse.pl: -------------------------------------------------------------------------------- 1 | 2 | =head1 PGcourse.pl 3 | 4 | # 5 | # Do course-specific initialization here. 6 | # (E.g. loading source.pl to get "show source" buttons 7 | # for example courses, and so on). 8 | # 9 | 10 | =cut 11 | 12 | sub _PGcourse_init { } 13 | 14 | #loadMacros("source.pl"); 15 | 1; 16 | -------------------------------------------------------------------------------- /macros/capa/StdConst.pg: -------------------------------------------------------------------------------- 1 | 2 | ## 3 | ## macros for Fundamental Constants, in SI units! 4 | ## 5 | ## Gravitational constant CapG: Units-> m3/kgs2 or Nm2/kg2 6 | $CapG = 6.67e-11 ; 7 | ## 8 | ## Speed of light in m/s: 9 | $smallc = 2.99792458e+8 ; 10 | ## 11 | ## Electron charge in C: 12 | $smalle = 1.602177e-19 ; 13 | ## 14 | ## Planck constant, smallh: Units ->Js 15 | $smallh = 6.626075e-34 ; 16 | ## 17 | ## Boltzmann constant, smallk: Units ->J/K 18 | $smallk = 1.38066e-23 ; 19 | ## 20 | ## standardgrav. accel sea level, smallg: Units ->m/s2 21 | $smallg = 9.8 ; 22 | 23 | ## 24 | ## Pi: 25 | $pi = 3.1415926536 ; 26 | ## 27 | ## From degrees to radians: 28 | $degrad = $pi / 180.0 ; 29 | ## 30 | ## From radians to degrees: 31 | $raddeg = 180.0 / $pi ; 32 | 33 | 1; 34 | ################################################# 35 | ## Processing time = 2 secs ( 1.96 usr 0.41 sys = 2.38 cpu) 36 | ################################################# 37 | -------------------------------------------------------------------------------- /macros/contexts/contextFunctionAssign.pl: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | #Description: macro to load parserAssignment and change the error 3 | # message to be more specific for a function. Requires 4 | # answers be submitted in the form: 5 | # y=formula or f(x)=formula 6 | ###################################################################### 7 | loadMacros("parserAssignment.pl"); 8 | 9 | sub parser::Assignment::Formula::cmp_equal { 10 | my $self = shift; 11 | my $ans = shift; 12 | Value::cmp_equal($self, $ans); 13 | if ($ans->{ans_message} =~ m/Your answer isn't.*it looks like/s) { 14 | $ans->{ans_message} = 15 | "Warning: Your answer should be of the form: '" . $self->{tree}{lop}->string . "= formula'"; 16 | } 17 | } 18 | 19 | ###################################################################### 20 | 21 | 1; 22 | -------------------------------------------------------------------------------- /macros/contexts/contextLeadingZero.pl: -------------------------------------------------------------------------------- 1 | loadMacros("contextLimitedNumeric.pl"); 2 | 3 | $context{LeadingZero} = Parser::Context->getCopy("LimitedNumeric"); 4 | $context{LeadingZero}->{name} = "LeadingZero"; 5 | $context{LeadingZero}->flags->set( 6 | NumberCheck => sub { 7 | my $self = shift; 8 | $self->Error("Decimals must have a number before the decimal point") 9 | if $self->{value_string} =~ m/^\./; 10 | } 11 | ); 12 | -------------------------------------------------------------------------------- /macros/contexts/contextPeriodic.pl: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # WeBWorK Online Homework Delivery System 3 | # Copyright © 2000-2024 The WeBWorK Project, https://github.com/openwebwork 4 | # 5 | # This program is free software; you can redistribute it and/or modify it under 6 | # the terms of either: (a) the GNU General Public License as published by the 7 | # Free Software Foundation; either version 2, or (at your option) any later 8 | # version, or (b) the "Artistic License" which comes with this package. 9 | # 10 | # This program is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the 13 | # Artistic License for more details. 14 | ################################################################################ 15 | 16 | =head1 NAME 17 | 18 | contextPeriodic.pl - [DEPRECATED] Features added to Real and Complex 19 | MathObjects classes. 20 | 21 | =head1 DESCRIPTION 22 | 23 | This file is no longer needed, as these features have been added to the 24 | Real and Complex MathObject classes. 25 | 26 | =head1 USAGE 27 | 28 | Context("Numeric"); 29 | $a = Real("pi/2")->with(period=>pi); 30 | $a->cmp # will match pi/2, 3pi/2 etc. 31 | 32 | Context("Complex"); 33 | $z0 = Real("i^i")->with(period=>2pi, logPeriodic=>1); 34 | $z0->cmp # will match exp(i*(ln(1) + Arg(pi/2) + 2k pi)) 35 | 36 | =cut 37 | 38 | 1; 39 | 40 | -------------------------------------------------------------------------------- /macros/core/MathObjects.pl: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # WeBWorK Online Homework Delivery System 3 | # Copyright © 2000-2024 The WeBWorK Project, https://github.com/openwebwork 4 | # 5 | # This program is free software; you can redistribute it and/or modify it under 6 | # the terms of either: (a) the GNU General Public License as published by the 7 | # Free Software Foundation; either version 2, or (at your option) any later 8 | # version, or (b) the "Artistic License" which comes with this package. 9 | # 10 | # This program is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the 13 | # Artistic License for more details. 14 | ################################################################################ 15 | 16 | =head1 NAME 17 | 18 | MathObjects.pl - Macro-based fronted to the MathObjects system. 19 | 20 | =head1 DESCRIPTION 21 | 22 | This file loads Parser.pl which in turn loads Value.pl The purpose of this file 23 | is to encourage the use of the name MathObjects instead of Parser (which is not 24 | as intuitive for those who don't know the history). 25 | 26 | It may later be used for other purposes as well. 27 | 28 | =head1 SEE ALSO 29 | 30 | L. 31 | 32 | =cut 33 | 34 | # ^uses loadMacros 35 | loadMacros("Parser.pl"); 36 | 37 | 1; 38 | -------------------------------------------------------------------------------- /macros/core/PGstandard.pl: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | PGstandard.pl - Load standard PG macro packages. 5 | 6 | =head1 SYNOPSIS 7 | 8 | loadMacros('PGstandard.pl'); 9 | 10 | =head1 DESCRIPTION 11 | 12 | PGstandard.pl loads the following macro files: 13 | 14 | =over 15 | 16 | =item * PG.pl 17 | 18 | =item * PGbasicmacros.pl 19 | 20 | =item * PGanswermacros.pl 21 | 22 | =item * PGauxiliaryFunctions.pl 23 | 24 | =item * customizeLaTeX.pl 25 | 26 | =back 27 | 28 | =cut 29 | 30 | loadMacros("PG.pl", "PGbasicmacros.pl", "PGanswermacros.pl", "PGauxiliaryFunctions.pl", "customizeLaTeX.pl",); 31 | 32 | 1; 33 | -------------------------------------------------------------------------------- /macros/deprecated/AnswerFormatHelp.pl: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | AnswerFormatHelp.pl 5 | 6 | =head1 SYNOPSIS 7 | 8 | THIS MACRO IS DEPRECATED. DO NOT USE THIS MACRO IN NEWLY WRITTEN PROBLEMS. 9 | Use C from PGbasicmacros.pl instead. 10 | 11 | Creates links for students to help documentation on formatting 12 | answers and allows for custom help links. 13 | 14 | =head1 DESCRIPTION 15 | 16 | There are 16 predefined help links: angles, decimals, equations, 17 | exponents, formulas, fractions, inequalities, intervals, limits, 18 | logarithms, matrices, numbers, points, syntax, units, vectors. 19 | 20 | Usage: 21 | 22 | DOCUMENT(); 23 | loadMacros("PGstandard.pl","AnswerFormatHelp.pl",); 24 | TEXT(beginproblem()); 25 | BEGIN_TEXT 26 | \{ ans_rule(20) \} 27 | \{ AnswerFormatHelp("formulas") \} $PAR 28 | \{ ans_rule(20) \} 29 | \{ AnswerFormatHelp("equations","help entering equations") \} $PAR 30 | END_TEXT 31 | ENDDOCUMENT(); 32 | 33 | 34 | The first example use defaults and displays the help link right next to 35 | the answer blank, which is recommended. The second example customizes 36 | the link text displayed to the student, but the actual help document 37 | is unaffected. 38 | 39 | =cut 40 | 41 | sub _AnswerFormatHelp_init { }; # don't reload this file 42 | 43 | sub AnswerFormatHelp { 44 | my ($helptype, $customstring) = @_; 45 | return helpLink($helptype, $customstring); 46 | } 47 | 48 | 1; 49 | -------------------------------------------------------------------------------- /macros/deprecated/BrockPhysicsMacros.pl: -------------------------------------------------------------------------------- 1 | # this file defines all Brock-Physics-specific macros. 2 | -------------------------------------------------------------------------------- /macros/deprecated/README.md: -------------------------------------------------------------------------------- 1 | # macros/deprecated folder 2 | 3 | This folder contains macros intended to be deprecated. These are either macros that do not 4 | contain functionality that is general for other pg problems (generally are specific for a particular 5 | university) or are no longer necessary (the functionality is either in other pg macros or webwork 6 | itself). 7 | -------------------------------------------------------------------------------- /macros/deprecated/problemPreserveAnswers.pl: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # WeBWorK Online Homework Delivery System 3 | # Copyright © 2000-2024 The WeBWorK Project, https://github.com/openwebwork 4 | # 5 | # This program is free software; you can redistribute it and/or modify it under 6 | # the terms of either: (a) the GNU General Public License as published by the 7 | # Free Software Foundation; either version 2, or (at your option) any later 8 | # version, or (b) the "Artistic License" which comes with this package. 9 | # 10 | # This program is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the 13 | # Artistic License for more details. 14 | ################################################################################ 15 | 16 | =head1 NAME 17 | 18 | problemPreserveAnswers.pl - Allow sticky answers to preserve special characters. 19 | 20 | =head1 DESCRIPTION 21 | 22 | This file implements a fragile hack to overcome a problem with 23 | PGbasicmacros.pl, which removes special characters from student 24 | answers (in order to prevent EV3 from mishandling them). 25 | 26 | NOTE: This file has been depreciated and doesn't do anything any more. 27 | Encoding of special characters is now handled by PGbasicmacros.pl 28 | 29 | =cut 30 | 31 | sub _problemPreserveAnswers_init { PreserveAnswers::Init() } 32 | 33 | package PreserveAnswers; 34 | 35 | sub Init { 36 | 37 | } 38 | 39 | our $ENDDOCUMENT; # holds pointer to original ENDDOCUMENT 40 | 41 | ###################################################################### 42 | 43 | 1; 44 | -------------------------------------------------------------------------------- /macros/math/fixedPrecision.pl: -------------------------------------------------------------------------------- 1 | sub _fixedPrecision_init { } 2 | loadMacros('MathObjects.pl'); 3 | 4 | package FixedPrecision; 5 | our @ISA = ("Value::Real"); 6 | 7 | sub new { 8 | my $self = shift; 9 | my $class = ref($self) || $self; 10 | my $context = (Value::isContext($_[0]) ? shift : $self->context); 11 | my $x = shift; 12 | my $n = shift; 13 | Value::Error("Too many arguments") if scalar(@_) > 0; 14 | if (defined($n)) { 15 | $x = main::prfmt($x, "%.${n}f"); 16 | } else { 17 | $x =~ s/\s+//g; 18 | my ($int, $dec) = split(/\./, $x); 19 | $n = length($dec); 20 | } 21 | $self = bless $self->SUPER::new($context, $x), $class; 22 | $self->{decimals} = $n; 23 | $self->{isValue} = 1; 24 | return $self; 25 | } 26 | 27 | sub string { 28 | my $self = shift; 29 | main::prfmt($self->value, "%." . $self->{decimals} . "f"); 30 | } 31 | 32 | sub compare { 33 | my ($self, $l, $r) = Value::checkOpOrder(@_); 34 | $l cmp $r; 35 | } 36 | 37 | package FixedPrecisionNumber; 38 | our @ISA = ("Parser::Number"); 39 | 40 | sub new { 41 | my $self = shift; 42 | my $class = ref($self) || $self; 43 | my $equation = shift; 44 | my $context = $equation->{context}; 45 | $self = bless $self->SUPER::new($equation, @_), $class; 46 | $self->{value} = FixedPrecision->new($self->{value_string}); 47 | return $self; 48 | } 49 | 50 | sub string { (shift)->{value}->string(@_) } 51 | sub TeX { (shift)->{value}->TeX(@_) } 52 | 53 | package main; 54 | 55 | Context()->{parser}{Number} = "FixedPrecisionNumber"; 56 | 57 | sub FixedPrecision { FixedPrecision->new(@_) } 58 | 59 | 1; 60 | -------------------------------------------------------------------------------- /macros/math/interpMacros.pl: -------------------------------------------------------------------------------- 1 | # interpMacros.pl 2 | # Rename this file (with .pl extension) and place it in your course macros directory, 3 | 4 | sub interpVals { 5 | $arrayLength = ($#_) / 2; 6 | @A_ARRAY = @_[ 0 .. ($arrayLength - 1) ]; 7 | @B_ARRAY = @_[ $arrayLength .. ($#_ - 1) ]; 8 | $A_VAL = $_[$#_]; 9 | $arrayLength2 = $#A_ARRAY; 10 | 11 | for ($i = 1; $i < ($#A_ARRAY + 1); $i++) { 12 | if ($A_VAL == $A_ARRAY[ $i - 1 ]) { 13 | $B_VAL = $B_ARRAY[ $i - 1 ]; 14 | last; 15 | } elsif ($A_ARRAY[ $i - 1 ] < $A_VAL && $A_VAL < $A_ARRAY[$i]) { 16 | $AL = $A_ARRAY[ $i - 1 ]; 17 | $AR = $A_ARRAY[$i]; 18 | $BL = $B_ARRAY[ $i - 1 ]; 19 | $BR = $B_ARRAY[$i]; 20 | $B_VAL = (($A_VAL - $AL) / ($AR - $AL) * ($BR - $BL)) + $BL; 21 | last; 22 | } elsif ($A_VAL == $A_ARRAY[$i]) { 23 | $B_VAL = $B_ARRAY[$i]; 24 | last; 25 | } else { 26 | $B_VAL = $B_ARRAY[0]; 27 | } 28 | } 29 | 30 | return $B_VAL; 31 | } 32 | 33 | 1; #required at end of file - a perl thing 34 | -------------------------------------------------------------------------------- /macros/misc/PGunion.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Load most of the interesting code developed at Union. 3 | # 4 | 5 | loadMacros( 6 | "unionMacros.pl", "unionUtils.pl", "unionProblem.pl", "unionImage.pl", 7 | "unionLists.pl", "unionTables.pl", "unionMessages.pl", 8 | ); 9 | 10 | 1; 11 | -------------------------------------------------------------------------------- /macros/misc/unionProblem.pl: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/perl 2 | 3 | sub _unionProblem_init { } 4 | 5 | ################################################## 6 | # 7 | # No longer needed since WW can output the grey 8 | # box automatically by changes in global.conf 9 | # 10 | sub BEGIN_PROBLEM { } 11 | sub END_PROBLEM { } 12 | 13 | sub problem_NoTable { } 14 | 15 | 1; 16 | -------------------------------------------------------------------------------- /macros/parsers/parserMultiPart.pl: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # WeBWorK Online Homework Delivery System 3 | # Copyright © 2000-2024 The WeBWorK Project, https://github.com/openwebwork 4 | # 5 | # This program is free software; you can redistribute it and/or modify it under 6 | # the terms of either: (a) the GNU General Public License as published by the 7 | # Free Software Foundation; either version 2, or (at your option) any later 8 | # version, or (b) the "Artistic License" which comes with this package. 9 | # 10 | # This program is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the 13 | # Artistic License for more details. 14 | ################################################################################ 15 | 16 | =head1 NAME 17 | 18 | parserMultiPart.pl - [DEPRECATED] Renamed to MultiAnswer. 19 | 20 | =head1 DESCRIPTION 21 | 22 | This object has been renamed MultiAnswer and is now available in 23 | parserMultiAnswer.pl. Using a MultiPart object will produce a 24 | warning to that effect. 25 | 26 | =cut 27 | 28 | sub _parserMultiPart_init { } 29 | 30 | loadMacros("parserMultiAnswer.pl"); 31 | 32 | sub MultiPart { 33 | warn "The MultiPart object has been deprecated.${BR}You should use MultiAnswer object instead"; 34 | parser::MultiAnswer->new(@_); 35 | } 36 | 37 | 1; 38 | -------------------------------------------------------------------------------- /macros/parsers/parserMultipleChoice.pl: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | parserMultipleChoice.pl - Load all the multiple choice parsers: PopUp, CheckboxList, RadioButtons, RadioMultiAnswer. 5 | 6 | =head1 SYNOPSIS 7 | 8 | loadMacros('parserMultipleChoice.pl'); 9 | 10 | =head1 DESCRIPTION 11 | 12 | parserMultipleChoice.pl loads the following macro files: 13 | 14 | =over 15 | 16 | =item * parserPopUp.pl 17 | 18 | =item * parserCheckboxList.pl 19 | 20 | =item * parserRadioButtons.pl 21 | 22 | =item * parserRadioMultiAnswer.pl 23 | 24 | =back 25 | 26 | =cut 27 | 28 | loadMacros("parserPopUp.pl", "parserCheckboxList.pl", "parserRadioButtons.pl", "parserRadioMultiAnswer.pl"); 29 | 30 | 1; 31 | -------------------------------------------------------------------------------- /macros/parsers/parserUtils.pl: -------------------------------------------------------------------------------- 1 | 2 | # not sure why these are loaded. They are not used in this file. If these are loaded 3 | # there is an error during the load_macros.t test. 4 | 5 | # loadMacros("unionImage.pl", "unionTables.pl",); 6 | 7 | $bHTML = '\begin{rawhtml}'; 8 | $eHTML = '\end{rawhtml}'; 9 | 10 | # HTML(htmlcode) 11 | # HTML(htmlcode,texcode) 12 | # 13 | # Insert $html in HTML mode or \begin{rawhtml}$html\end{rawhtml} in 14 | # Latex2HTML mode. In TeX mode, insert nothing for the first form, and 15 | # $tex for the second form. 16 | # 17 | sub HTML { 18 | my ($html, $tex) = @_; 19 | return ('') unless (defined($html) && $html ne ''); 20 | $tex = '' unless (defined($tex)); 21 | MODES(TeX => $tex, Latex2HTML => $bHTML . $html . $eHTML, HTML => $html); 22 | } 23 | 24 | # 25 | # Begin and end mode 26 | # 27 | $BTT = HTML('', '\texttt{'); 28 | $ETT = HTML('', '}'); 29 | 30 | # 31 | # Begin and end mode 32 | # 33 | $BSMALL = HTML('', '{\small '); 34 | $ESMALL = HTML('', '}'); 35 | 36 | # 37 | # Block quotes 38 | # 39 | $BBLOCKQUOTE = HTML('
', '\hskip3em '); 40 | $EBLOCKQUOTE = HTML('
'); 41 | 42 | # 43 | # Smart-quotes in TeX mode, regular quotes in HTML mode 44 | # 45 | $LQ = MODES(TeX => '``', Latex2HTML => '"', HTML => '"'); 46 | $RQ = MODES(TeX => "''", Latex2HTML => '"', HTML => '"'); 47 | 48 | # 49 | # make sure all characters are displayed 50 | # 51 | sub protectHTML { 52 | my $string = shift; 53 | $string =~ s/&/\&/g; 54 | $string =~ s//\>/g; 56 | $string; 57 | } 58 | 59 | sub _parserUtils_init { } 60 | 61 | 1; 62 | 63 | -------------------------------------------------------------------------------- /macros/ui/pccTables.pl: -------------------------------------------------------------------------------- 1 | # This is just a redirect to niceTables.pl, which was originally called pccTables.pl 2 | 3 | loadMacros("niceTables.pl"); 4 | 5 | 1; 6 | -------------------------------------------------------------------------------- /macros/ui/source.pl: -------------------------------------------------------------------------------- 1 | if ($displayMode =~ m/HTML/ && !defined($_slides_loaded)) { 2 | TEXT('
' 3 | . '' 9 | . '' 10 | . '
'); 11 | } 12 | 13 | sub NoSourceButton { 14 | # if ($displayMode =~ m/HTML/) { 15 | # TEXT(''); 16 | # } 17 | } 18 | 19 | =head1 sourceButton 20 | 21 | activating the source button 22 | 23 | In order for this button to work the course needs to have a link from 24 | 25 | myCourse/html/show-source.cgi to webwork2/htdocs/show-source.cgi 26 | 27 | in the directory myCourse/html. To create this link execute the following 28 | command (you will need command line access to the server to do this) 29 | 30 | ln -s /opt/webwork/webwork2/htdocs/show-source.cgi show-source.cgi 31 | 32 | You need to make sure that the file webwork2/htdocs/show-source.cgi is executable by the 33 | apache webserver. 34 | 35 | To accomplish this you need to uncomment this line in webwork.apache2-config 36 | 37 | ScriptAliasMatch /webwork2_course_files/([^/]*)/show-source.cgi/(.*) /opt/webwork/courses/$1/html/show-source.cgi/$2 38 | 39 | The show-source.cgi script may also have to be customized to set C<$root> to the webwork2 directory 40 | 41 | =cut 42 | 43 | 1; 44 | -------------------------------------------------------------------------------- /t/build_PG_envir.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | die "PG_ROOT not found in environment.\n" unless $ENV{PG_ROOT}; 7 | use lib "$ENV{PG_ROOT}/lib"; 8 | 9 | my $macros_dir = "$ENV{PG_ROOT}/macros"; 10 | 11 | use WeBWorK::PG::Environment; 12 | use WeBWorK::PG; 13 | use PGcore; 14 | use Parser; 15 | 16 | %main::envir = %{ WeBWorK::PG::defineProblemEnvironment(WeBWorK::PG::Environment->new) }; 17 | 18 | sub PG_restricted_eval { 19 | my @input = @_; 20 | return WeBWorK::PG::Translator::PG_restricted_eval(@input); 21 | } 22 | 23 | sub check_score { 24 | my ($correct_answer, $ans) = @_; 25 | return $correct_answer->cmp->evaluate($ans)->{score}; 26 | } 27 | 28 | do "$macros_dir/PG.pl"; 29 | 30 | DOCUMENT(); 31 | 32 | loadMacros('PGbasicmacros.pl'); 33 | 34 | 1; 35 | -------------------------------------------------------------------------------- /t/contexts/fraction.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | =head1 Fraction context 4 | 5 | Test the fraction context defined in contextFraction.pl. 6 | 7 | =cut 8 | 9 | use Test2::V0 '!E', { E => 'EXISTS' }; 10 | 11 | die "PG_ROOT not found in environment.\n" unless $ENV{PG_ROOT}; 12 | do "$ENV{PG_ROOT}/t/build_PG_envir.pl"; 13 | 14 | use lib "$ENV{PG_ROOT}/lib"; 15 | 16 | loadMacros('PGstandard.pl', 'MathObjects.pl', 'contextFraction.pl'); 17 | 18 | use Value; 19 | require Parser::Legacy; 20 | import Parser::Legacy; 21 | 22 | Context('Fraction'); 23 | 24 | subtest 'contextFraction: Basic computation and reduction' => sub { 25 | ok my $a1 = Compute('1/2'), 'compute 1/2'; 26 | ok my $a2 = Compute('2/4'), 'compute 2/4'; 27 | 28 | is $a1->value, $a2->value, 'comparison (1/2 = 2/4)'; 29 | }; 30 | 31 | subtest 'contextFraction: Conversion of real to fraction' => sub { 32 | my ($result, $direct); 33 | for my $num (1 .. 100) { 34 | for my $den (1 .. 100) { 35 | my $real = Real($num / $den); 36 | push(@$result, Fraction($real)->value); 37 | push(@$direct, Fraction($num, $den)->value); 38 | } 39 | } 40 | 41 | is $result, $direct, 'converted real gives correct fraction'; 42 | }; 43 | 44 | done_testing(); 45 | -------------------------------------------------------------------------------- /t/contexts/integer.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | =head1 Integer context 4 | 5 | Test contextInteger.pl methods. 6 | 7 | =cut 8 | 9 | use Test2::V0 '!E', { E => 'EXISTS' }; 10 | 11 | die "PG_ROOT not found in environment.\n" unless $ENV{PG_ROOT}; 12 | do "$ENV{PG_ROOT}/t/build_PG_envir.pl"; 13 | 14 | use lib "$ENV{PG_ROOT}/lib"; 15 | 16 | loadMacros('MathObjects.pl', 'contextInteger.pl'); 17 | 18 | use Value; 19 | require Parser::Legacy; 20 | import Parser::Legacy; 21 | 22 | Context('Integer'); 23 | 24 | my $b = Compute(gcd(5, 2)); 25 | ANS($b->cmp); 26 | 27 | ok(1, 'integer test: dummy test'); 28 | 29 | done_testing(); 30 | -------------------------------------------------------------------------------- /t/contexts/trig_degrees.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | =head1 contextTrigDegrees 4 | 5 | Test computations in the TrigDegrees context. 6 | 7 | =cut 8 | 9 | use Test2::V0 '!E', { E => 'EXISTS' }; 10 | 11 | die "PG_ROOT not found in environment.\n" unless $ENV{PG_ROOT}; 12 | do "$ENV{PG_ROOT}/t/build_PG_envir.pl"; 13 | 14 | loadMacros('contextTrigDegrees.pl'); 15 | 16 | my $ctx = Context('TrigDegrees'); 17 | 18 | ok(Value::isContext($ctx), 'trig degrees: check context'); 19 | 20 | ok my $cos60 = Compute('cos(60)'), 'Call Compute'; 21 | ok my $eval_cos60 = $cos60->cmp->evaluate('1/2'), 'evalute an answer to cos(60)'; 22 | 23 | is $eval_cos60, hash { 24 | field type => 'Value (Real)'; 25 | field score => 1; 26 | field correct_ans => 'cos(60)'; 27 | field student_ans => 0.5; 28 | field error_flag => U(); 29 | field error_message => DF(); 30 | etc(); 31 | }, q{What does the Compute('cos(60)')->cmp->evaluate('1/2') object look like?}; 32 | 33 | is(check_score($cos60, '1/2'), 1, 'trig degrees: cos(60) = 1/2'); 34 | is(check_score(Compute('cos(60)'), 'sin(30)'), 1, 'trig degrees: cos(60) = 1/2'); 35 | is check_score(Compute('sin(0)'), '0'), 1, 'trig degrees: sin(0) = 0'; 36 | is check_score(Compute('sin(90)'), '1'), 1, 'trig degrees: sin(90) = 1'; 37 | is check_score(Compute('cos(0)'), '1'), 1, 'trig degrees: cos(0) = 1'; 38 | is check_score(Compute('cos(90)'), '0'), 1, 'trig degrees: cos(90) = 0'; 39 | is check_score(Compute('cos(90)'), '1.6155E-15'), 1, 'trig degrees: cos(90) ~ 0'; 40 | 41 | is check_score(Compute("cos($main::PI/3)"), "sin($main::PI/6)"), 0, 'trig degrees: cos(pi/3) != sin(pi/6)'; 42 | 43 | done_testing(); 44 | -------------------------------------------------------------------------------- /t/latex_image_test/latex_image_test1.pg: -------------------------------------------------------------------------------- 1 | ##DESCRIPTION 2 | # TEST tikz from a pg problem 3 | ##ENDDESCRIPTION 4 | 5 | DOCUMENT(); 6 | 7 | loadMacros( 8 | "PGstandard.pl", 9 | "MathObjects.pl", 10 | "PGlateximage.pl" 11 | ); 12 | 13 | TEXT(beginproblem()); 14 | 15 | ############################################################## 16 | # Setup 17 | ############################################################## 18 | 19 | $drawing = createLaTeXImage(); 20 | $drawing->texPackages([['xy','all']]); 21 | $drawing->BEGIN_LATEX_IMAGE 22 | \xymatrix{ A \ar[r] & B \ar[d] \\\\ 23 | D \ar[u] & C \ar[l] } 24 | END_LATEX_IMAGE 25 | 26 | $path = insertGraph($drawing); 27 | 28 | Context("Numeric"); 29 | 30 | ############################################################## 31 | # Text 32 | ############################################################## 33 | 34 | BEGIN_TEXT 35 | \{protect_underbar("path = $path")\}; 36 | $BR alias = \{protect_underbar(alias($path))\} 37 | $PAR image = \{image($path, width => 228, height => 114, tex_size => 400)\} 38 | $PAR svg = \{embedSVG($path)\} 39 | END_TEXT 40 | 41 | ENDDOCUMENT(); 42 | -------------------------------------------------------------------------------- /t/latex_image_test/latex_image_test2.pg: -------------------------------------------------------------------------------- 1 | ##DESCRIPTION 2 | # TEST tikz from a pg problem 3 | ##ENDDESCRIPTION 4 | 5 | DOCUMENT(); 6 | 7 | loadMacros( 8 | "PGstandard.pl", 9 | "MathObjects.pl", 10 | "PGlateximage.pl" 11 | ); 12 | 13 | TEXT(beginproblem()); 14 | 15 | ############################################################## 16 | # Setup 17 | ############################################################## 18 | 19 | $drawing = createLaTeXImage(); 20 | $drawing->texPackages(['circuitikz']); 21 | $drawing->environment(['circuitikz','scale=1.2, transform shape']); 22 | $drawing->BEGIN_LATEX_IMAGE 23 | \draw (60,1) to [battery2, v_=\(V_{cc}\), name=B] ++(0,2); 24 | \node[draw,red,circle,inner sep=4pt] at(B.left) {}; 25 | \node[draw,red,circle,inner sep=4pt] at(B.right) {}; 26 | END_LATEX_IMAGE 27 | 28 | $path = insertGraph($drawing); 29 | 30 | Context("Numeric"); 31 | 32 | ############################################################## 33 | # Text 34 | ############################################################## 35 | 36 | BEGIN_TEXT 37 | \{protect_underbar("path = $path")\}; 38 | $BR alias = \{protect_underbar(alias($path))\} 39 | $PAR image = \{image($path, width => 228, height => 114, tex_size => 400)\} 40 | $PAR svg = \{embedSVG($path)\} 41 | END_TEXT 42 | 43 | ENDDOCUMENT(); 44 | 45 | -------------------------------------------------------------------------------- /t/macros/basicmacros.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 '!E', { E => 'EXISTS' }; 4 | 5 | die "PG_ROOT not found in environment.\n" unless $ENV{PG_ROOT}; 6 | do "$ENV{PG_ROOT}/t/build_PG_envir.pl"; 7 | 8 | use HTML::Entities; 9 | use HTML::TagParser; 10 | 11 | my $name = NEW_ANS_NAME(); 12 | 13 | my $html = HTML::TagParser->new(NAMED_ANS_RULE($name)); 14 | 15 | my @inputs = $html->getElementsByTagName('input'); 16 | is($inputs[0]->attributes->{id}, $name, 'basicmacros: test NAMED_ANS_RULE id attribute'); 17 | is($inputs[0]->attributes->{name}, $name, 'basicmacros: test NAMED_ANS_RULE name attribute'); 18 | is($inputs[0]->attributes->{type}, 'text', 'basicmacros: test NAMED_ANS_RULE type attribute'); 19 | ok(!$inputs[0]->attributes->{value}, 'basicmacros: test NAMED_ANS_RULE value attribute'); 20 | 21 | is($inputs[1]->attributes->{name}, "previous_$name", 'basicmacros: test NAMED_ANS_RULE hidden name attribute'); 22 | is($inputs[1]->attributes->{type}, 'hidden', 'basicmacros: test NAMED_ANS_RULE hidden type attribute'); 23 | 24 | done_testing(); 25 | -------------------------------------------------------------------------------- /t/macros/math_objects_more.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | =head1 MathObjects 4 | 5 | Test more MathObject properties and operations. 6 | 7 | =cut 8 | 9 | use Test2::V0 '!E', { E => 'EXISTS' }; 10 | 11 | die "PG_ROOT not found in environment.\n" unless $ENV{PG_ROOT}; 12 | do "$ENV{PG_ROOT}/t/build_PG_envir.pl"; 13 | 14 | loadMacros('MathObjects.pl'); 15 | 16 | my $ctx = Context('Numeric'); 17 | 18 | ok(Value::isContext($ctx), 'math objects: check context'); 19 | 20 | my $f = Compute('x^2'); 21 | my $g = Compute('sin(x)'); 22 | 23 | ok(Value::isFormula($f), 'math objects: check for formula'); 24 | is($f->class, 'Formula', 'math objects: check that the class is Formula'); 25 | is($f->type, 'Number', 'math objects: check that the type is Number'); 26 | 27 | # check answer evaluators 28 | is(check_score($f->eval(x => 2), '4'), 1, 'math objects: eval x^2 at x=2'); 29 | is(check_score($f->eval(x => -3), '9'), 1, 'math objects: eval x^2 at x=-3'); 30 | is(check_score($g->eval(x => 'pi/6'), '1/2'), 1, 'math objects: eval sin(x) at x=pi/6'); 31 | 32 | # check derivatives 33 | is(check_score($f->D('x'), '2x'), 1, 'math objects: derivative of x^2'); 34 | is(check_score($g->D('x'), 'cos(x)'), 1, 'math objects: derivative of sin(x)'); 35 | 36 | done_testing(); 37 | -------------------------------------------------------------------------------- /t/matrix_tableau_tests/print_tableau_Test.pg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ############################################## 5 | DOCUMENT(); 6 | 7 | loadMacros( 8 | "PGstandard.pl", # Standard macros for PG language 9 | "MathObjects.pl", 10 | "parserLinearInequality.pl", 11 | "PGML.pl", 12 | "tableau.pl", 13 | "PGmatrixmacros.pl", 14 | "LinearProgramming.pl", 15 | #"source.pl", # allows code to be displayed on certain sites. 16 | "PGcourse.pl", 17 | ); 18 | 19 | ############################################## 20 | 21 | Context("Matrix"); # need Matrix context to allow string input into Matrix. 22 | 23 | $m = Matrix("[[3,6,7],[2,1,8],[4,6,21],[-6,7,9]]"); 24 | $constraint_matrix = Matrix(" 25 | [[ 0, 0, -1, -1], 26 | [-1, -1, 0, 0 ], 27 | [1, 0 , 1 , 0], 28 | [0, 1, 0, 1]] 29 | "); 30 | 31 | #TEXT ("created ". ref($m)); 32 | #what are the best ways to display a matrix? 33 | 34 | $m1 = matrix_from_matrix_rows($m, [1,3,2]); 35 | $m2 = matrix_from_matrix_cols($m, [3,2,1]); 36 | $m3 = matrix_from_submatrix($m, rows=>[1,4], columns=>[1,2]); 37 | 38 | $list = matrix_rows_to_list($m, 2,3); 39 | 40 | $b = Matrix([1, 2, 3, 4]); 41 | TEXT($BR, "vector", $b->data->[1]); 42 | $c = Matrix([5, 6, 7]); 43 | $t = Tableau->new(A=>$m,b=>$b, c=>$c); 44 | 45 | $basis2 = $t->basis(1,3,5,6); 46 | 47 | BEGIN_PGML 48 | 49 | [` [$m] `] 50 | 51 | Extract rows (1, 4 ) from Matrix 52 | 53 | [` [$m1] `] 54 | 55 | Extract the submatrix of rows (1,4) and columns (1, 2, 3) 56 | 57 | new submatrix is 58 | [` [$m3] `] 59 | 60 | a list of rows 2 and 3 [`[$list]`] 61 | 62 | A matrix from rows (1,3,2) [`[$m1]`] 63 | A matrix from cols (3,2,1) [`[$m2]`] 64 | 65 | A new matrix [`[$b]`] 66 | END_PGML 67 | 68 | ENDDOCUMENT(); 69 | 70 | -------------------------------------------------------------------------------- /t/pg_problems/source.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | die "PG_ROOT not found in environment.\n" unless $ENV{PG_ROOT}; 4 | use lib "$ENV{PG_ROOT}/lib"; 5 | 6 | use Test2::V0; 7 | 8 | use WeBWorK::PG; 9 | 10 | my $source = << 'END_SOURCE'; 11 | DOCUMENT(); 12 | 13 | loadMacros('PGstandard.pl', 'MathObjects.pl', 'PGML.pl', 'PGcourse.pl'); 14 | 15 | $pi = Real('pi'); 16 | 17 | BEGIN_PGML 18 | Enter a value for [`\pi`]. 19 | 20 | [_____]{$pi} 21 | END_PGML 22 | 23 | ENDDOCUMENT(); 24 | END_SOURCE 25 | 26 | ok my $pg = WeBWorK::PG->new(r_source => \$source, problemSeed => 1234, processAnswers => 1), 'source string renders'; 27 | 28 | my $correct_answers = 29 | { map { $_ => $pg->{answers}{$_}{correct_ans} } @{ $pg->{translator}{PG_FLAGS_REF}{ANSWER_ENTRY_ORDER} } }; 30 | 31 | ok my $pg2 = WeBWorK::PG->new( 32 | r_source => \$source, 33 | processAnswers => 1, 34 | inputs_ref => $correct_answers 35 | ), 36 | 'source string renders with answers passed'; 37 | 38 | is($pg2->{result}{score}, 1, 'correct answer is correct'); 39 | 40 | done_testing; 41 | -------------------------------------------------------------------------------- /t/pg_test_problems/alternateDecimalContext.pg: -------------------------------------------------------------------------------- 1 | DOCUMENT(); 2 | loadMacros( 3 | "PGstandard.pl", # Standard macros for PG language 4 | "MathObjects.pl", 5 | "contextAlternateDecimal.pl" 6 | ); 7 | 8 | Context("Matrix"); 9 | context::AlternateDecimal->Default(",",","); 10 | 11 | TEXT(beginproblem()); 12 | $pi = Compute("[[3,14; 2,18]; [3,22; 3,14*x^(2,12)]]"); 13 | Context()->texStrings; 14 | BEGIN_TEXT 15 | \($pi\) and \{$pi->ans_rule\} $BR 16 | END_TEXT 17 | Context()->normalStrings; 18 | ANS($pi->cmp); 19 | ENDDOCUMENT(); -------------------------------------------------------------------------------- /t/pg_test_problems/blankProblem.pg: -------------------------------------------------------------------------------- 1 | DOCUMENT(); 2 | 3 | loadMacros('PGstandard.pl', 'MathObjects.pl', 'PGML.pl', 'PGcourse.pl'); 4 | 5 | $pi = Real('pi'); 6 | 7 | BEGIN_PGML 8 | Enter a value for [`\pi`]. 9 | 10 | [_____]{$pi} 11 | END_PGML 12 | 13 | ENDDOCUMENT(); 14 | -------------------------------------------------------------------------------- /t/pg_test_problems/inequalitySetBuilderContext.pg: -------------------------------------------------------------------------------- 1 | DOCUMENT(); 2 | loadMacros( 3 | "PGstandard.pl", # Standard macros for PG language 4 | "MathObjects.pl", 5 | "contextAlternateIntervals.pl", 6 | "contextInequalitySetBuilder.pl" 7 | ); 8 | Context("InequalitySetBuilder"); 9 | context::AlternateIntervals->Enable; 10 | Context()->flags->set(enterIntervals=>"either", displayIntervals=>"alternate"); 11 | $S1 = Compute("{ x : 1 < x <= 4 }"); 12 | $S2 = SetBuilder("]1,4["); # force interval to be set in set-builder notation 13 | $S3 = Interval("[1,4["); 14 | $S4 = Compute("{ x : x < -2 or x > 2 }"); # forms the Union (-inf,-2) U (2,inf) 15 | $S5 = Compute("{ x : x > 2 and x <= 4 }"); # forms the Interval (2,4] 16 | $S6 = Compute("{ x : x = 1 }"); # forms the Set {1} 17 | $S7 = Compute("{ x : x != 1 }"); # forms the Union (-inf,1) U (1,inf) 18 | Context()->texStrings; 19 | BEGIN_TEXT 20 | \($S1\) \{$S1->ans_rule\} $BR 21 | \($S2\) \{$S2->ans_rule\} $BR 22 | \($S3\) \{$S3->ans_rule\} $BR 23 | \($S4\) \{$S4->ans_rule\} $BR 24 | \($S5\) \{$S5->ans_rule\} $BR 25 | \($S6\) \{$S6->ans_rule\} $BR 26 | \($S7\) \{$S7->ans_rule\} $BR 27 | END_TEXT 28 | Context()->normalStrings; 29 | ANS($S1->cmp()); 30 | ANS($S2->cmp()); 31 | ANS($S3->cmp()); 32 | ANS($S4->cmp()); 33 | ANS($S5->cmp()); 34 | ANS($S6->cmp()); 35 | ANS($S7->cmp()); 36 | ENDDOCUMENT(); 37 | -------------------------------------------------------------------------------- /t/pg_test_problems/settest_notnull_and_prettyprint/listVariables.pg: -------------------------------------------------------------------------------- 1 | ##DESCRIPTION 2 | ## Algebra problem: true or false for inequality 3 | ##ENDDESCRIPTION 4 | 5 | ##KEYWORDS('algebra', 'inequality', 'fraction') 6 | 7 | ## DBsubject('Algebra') 8 | ## DBchapter('Fundamentals') 9 | ## DBsection('Real Numbers') 10 | ## Date('6/3/2002') 11 | ## Author('') 12 | ## Institution('') 13 | ## TitleText1('Precalculus') 14 | ## EditionText1('3') 15 | ## AuthorText1('Stewart, Redlin, Watson') 16 | ## Section1('1.1') 17 | ## Problem1('22') 18 | 19 | ######################################################################## 20 | 21 | DOCUMENT(); 22 | 23 | loadMacros( 24 | "PGstandard.pl", # Standard macros for PG language 25 | "MathObjects.pl", 26 | "PGinfo.pl", 27 | #"source.pl", # allows code to be displayed on certain sites. 28 | #"PGcourse.pl", # Customization file for the course 29 | ); 30 | 31 | # Print problem number and point value (weight) for the problem 32 | TEXT(beginproblem()); 33 | 34 | # Show which answers are correct and which ones are incorrect 35 | $showPartialCorrectAnswers = 1; 36 | 37 | ############################################################## 38 | # 39 | # Setup 40 | # 41 | # 42 | Context("Numeric"); 43 | 44 | 45 | 46 | ############################################################## 47 | # 48 | # Text 49 | # 50 | # 51 | 52 | Context()->texStrings; 53 | BEGIN_TEXT 54 | Test pretty print using listVariables() subroutine. 55 | $BR 56 | 57 | 58 | END_TEXT 59 | Context()->normalStrings; 60 | listVariables(); 61 | ############################################################## 62 | # 63 | # Answers 64 | # 65 | # 66 | 67 | # relative tolerance --3.1412 is incorrect but 3.1413 is correct 68 | # default tolerance is .01 or one percent. 69 | 70 | 71 | ENDDOCUMENT(); 72 | -------------------------------------------------------------------------------- /t/pg_test_problems/settest_notnull_and_prettyprint/settest_notnull_and_prettyprint.def: -------------------------------------------------------------------------------- 1 | 2 | openDate = 09/03/2014 at 10:34am EDT 3 | dueDate = 09/10/2014 at 10:34am EDT 4 | answerDate = 09/10/2014 at 10:34am EDT 5 | paperHeaderFile = defaultHeader 6 | screenHeaderFile = defaultHeader 7 | description = Test the notnull() function. This returns 0 for an undefined variable, an empty string, a blank string, an empty array and an empty hash.--------Test the $PG-> pretty_print(object, html/text/TeX, level) function for pretty printing objects. When called directly as a subroutine (without $PG->) it uses level=5 and the current displayMode to print the object. 8 | problemList = 9 | settest_notnull_and_prettyprint/listVariables.pg, 1, -1 10 | settest_notnull_and_prettyprint/test_not_nullpg.pg, 1, -1 11 | settest_notnull_and_prettyprint/pretty_print_html.pg, 1, -1 12 | settest_notnull_and_prettyprint/pretty_print_text.pg, 1, -1 13 | settest_notnull_and_prettyprint/pretty_print_tex.pg, 1, -1 14 | 15 | -------------------------------------------------------------------------------- /t/test_find_file_in_directories.pl: -------------------------------------------------------------------------------- 1 | #!/Volumes/WW_test/opt/local/bin/perl -w 2 | 3 | use strict; 4 | 5 | BEGIN { 6 | die "WEBWORK_ROOT not found in environment. \n 7 | WEBWORK_ROOT can be defined in your .cshrc or .bashrc file\n 8 | It should be set to the webwork2 directory (e.g. /opt/webwork/webwork2)" 9 | unless exists $ENV{WEBWORK_ROOT}; 10 | # Unused variable, but define it twice to avoid an error message. 11 | $WeBWorK::Constants::WEBWORK_DIRECTORY = $ENV{WEBWORK_ROOT}; 12 | 13 | # Define MP2 -- this would normally be done in webwork.apache2.4-config 14 | $ENV{MOD_PERL_API_VERSION} = 2; 15 | print "Webwork root directory is $WeBWorK::Constants::WEBWORK_DIRECTORY\n\n"; 16 | 17 | $WebworkBase::courseName = "gage_test"; 18 | my $topDir = $WeBWorK::Constants::WEBWORK_DIRECTORY; 19 | $topDir =~ s|webwork2?$||; # remove webwork2 link 20 | $WebworkBase::RootWebwork2Dir = "$topDir/webwork2"; 21 | $WebworkBase::RootPGDir = "$topDir/pg"; 22 | $WebworkBase::RootCourseDir = "${topDir}courses"; 23 | 24 | eval "use lib '$WebworkBase::RootWebwork2Dir/lib'"; 25 | die $@ if $@; 26 | eval "use lib '$WebworkBase::RootPGDir/lib'"; 27 | die $@ if $@; 28 | } 29 | use PGalias; 30 | 31 | my $file = "prob14.html"; 32 | my @directories = ( 33 | "$WebworkBase::RootCourseDir/$WebworkBase::courseName/templates/setaliasCheck/htmlAliasCheck", 34 | "$WebworkBase::RootCourseDir/$WebworkBase::courseName/html", 35 | "$WebworkBase::RootWebwork2Dir/htdocs", 36 | ); 37 | my $file_path = PGalias->find_file_in_directories($file, \@directories) // 'not found'; 38 | print "File found at: $file_path\n"; 39 | 40 | 1; 41 | -------------------------------------------------------------------------------- /t/tikz_test/tikz_image.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 '!E', { E => 'EXISTS' }; 4 | 5 | # Quell a warning about this being used only once. 6 | local $main::envir; 7 | 8 | die "PG_ROOT not found in environment.\n" unless $ENV{PG_ROOT}; 9 | do "$ENV{PG_ROOT}/t/build_PG_envir.pl"; 10 | 11 | use lib "$ENV{PG_ROOT}/lib"; 12 | 13 | use LaTeXImage; 14 | 15 | loadMacros('PGtikz.pl'); 16 | 17 | my $drawing = createTikZImage(); 18 | $drawing->tex(<< 'END_TIKZ'); 19 | \draw (-4,0) -- (4,0); 20 | \draw (0,-2) -- (0,2); 21 | \draw (0,0) circle[radius=1.5]; 22 | \draw (0, 1.5) node[anchor=south]{N} -- (2.5,0) node[above]{y}; 23 | \draw (1.2,0.9) node[right]{\((\vec x, x_{n})\)}; 24 | END_TIKZ 25 | 26 | ok my $img = image($drawing), 'img tag is generated'; 27 | 28 | like $img, qr! 29 | ^$!x, 'img tag has correct format'; 36 | 37 | # Note that the image file is not generated until after the `image($drawing)` call. 38 | my $image_file = "$main::envir{tempDirectory}images/" . $drawing->imageName . '.' . $drawing->ext; 39 | ok -e $image_file, 'image file is generated'; 40 | 41 | # Delete the generated image file. 42 | unlink $image_file; 43 | 44 | done_testing(); 45 | -------------------------------------------------------------------------------- /t/tikz_test/tikz_test1.pg: -------------------------------------------------------------------------------- 1 | ##DESCRIPTION 2 | # TEST tikz from a pg problem 3 | ##ENDDESCRIPTION 4 | 5 | DOCUMENT(); 6 | 7 | loadMacros( 8 | "PGstandard.pl", 9 | "MathObjects.pl", 10 | "PGtikz.pl" 11 | ); 12 | 13 | TEXT(beginproblem()); 14 | 15 | ############################################################## 16 | # Setup 17 | ############################################################## 18 | 19 | $drawing = createTikZImage(); 20 | $drawing->tex(< 228, height => 114, tex_size => 400)\} 40 | $PAR svg = \{embedSVG($path)\} 41 | END_TEXT 42 | 43 | ENDDOCUMENT(); 44 | -------------------------------------------------------------------------------- /t/tikz_test/tikz_test2.pg: -------------------------------------------------------------------------------- 1 | ##DESCRIPTION 2 | # TEST tikz from a pgml problem 3 | ##ENDDESCRIPTION 4 | 5 | DOCUMENT(); 6 | 7 | loadMacros( 8 | "PGstandard.pl", 9 | "MathObjects.pl", 10 | "PGML.pl", 11 | "PGtikz.pl" 12 | ); 13 | 14 | TEXT(beginproblem()); 15 | 16 | ############################################################## 17 | # Setup 18 | ############################################################## 19 | 20 | 21 | $drawing = createTikZImage(); 22 | $drawing->tikzOptions("main_node/.style={circle,fill=blue!20,draw,minimum size=1em,inner sep=3pt}"); 23 | $drawing->tex(< 100, tex_size => 400) @]* 44 | 45 | svg = [@ embedSVG($path) @]* 46 | END_PGML 47 | 48 | ENDDOCUMENT(); 49 | -------------------------------------------------------------------------------- /t/tikz_test/tikz_test3.pg: -------------------------------------------------------------------------------- 1 | ##DESCRIPTION 2 | # TEST tikz from a pgml problem 3 | ##ENDDESCRIPTION 4 | 5 | DOCUMENT(); 6 | 7 | loadMacros( 8 | "PGstandard.pl", 9 | "MathObjects.pl", 10 | "PGML.pl", 11 | "PGtikz.pl" 12 | ); 13 | 14 | TEXT(beginproblem()); 15 | 16 | ############################################################## 17 | # Setup 18 | ############################################################## 19 | 20 | $a = random(1, 4); 21 | $b = random(3, 6); 22 | $c = random(5, 8); 23 | $d = random(7, 10); 24 | 25 | $tikz_code = <texPackages([["pgfplots"]]); 49 | $drawing->addToPreamble("\pgfplotsset{compat=1.15}"); 50 | $drawing->tikzOptions("main_node/.style={circle,fill=blue!20,draw,minimum size=1em,inner sep=3pt}"); 51 | $drawing->tex($tikz_code); 52 | 53 | 54 | $path = insertGraph($drawing); 55 | 56 | Context("Numeric"); 57 | 58 | ############################################################## 59 | # Text 60 | ############################################################## 61 | 62 | BEGIN_PGML 63 | path = [@ protect_underbar($path) @] 64 | [@ $BR @]* 65 | alias = [@ protect_underbar(alias($path)) @]* 66 | 67 | image = [@ image($path, width => 100, tex_size => 400) @]* 68 | 69 | svg = [@ embedSVG($path) @]* 70 | END_PGML 71 | 72 | ENDDOCUMENT(); 73 | -------------------------------------------------------------------------------- /t/units/add_units.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 '!E', { E => 'EXISTS' }; 4 | 5 | die "PG_ROOT not found in environment.\n" unless $ENV{PG_ROOT}; 6 | do "$ENV{PG_ROOT}/t/build_PG_envir.pl"; 7 | 8 | use lib "$ENV{PG_ROOT}/lib"; 9 | 10 | use Units; 11 | use Parser::Legacy::NumberWithUnits; 12 | 13 | loadMacros('parserNumberWithUnits.pl'); 14 | 15 | my $Flops = { name => 'flops', conversion => { factor => 1, s => -1 } }; 16 | my $bogomips; 17 | 18 | my $inv_sec = NumberWithUnits(4, 's^-1'); 19 | 20 | subtest 'Unknown unit' => sub { 21 | like 22 | dies { $bogomips = NumberWithUnits(4, 'flops') }, 23 | qr/^Unrecognizable unit: \|flops\|/, 24 | "Dies if it can't find the unit"; 25 | }; 26 | 27 | subtest 'Add a new unit' => sub { 28 | my $todo = todo 'This will work when adding a new unit is fixed'; 29 | ok( 30 | #lives { $bogomips = NumberWithUnits( 4, 'flops', {newUnit => $Flops}) }, 31 | lives { $bogomips = NumberWithUnits(4, 'flops', { newUnit => 'flops' }) }, 32 | "Can add a new unit in NumberWithUnits" 33 | ) or note($@); 34 | 35 | ok(lives { check_score($bogomips, $inv_sec) }, 'This will work when adding a new unit is fixed'); 36 | }; 37 | 38 | subtest 'Overwrite an existing unit' => sub { 39 | my $Hurts = { name => 'Hz', conversion => { factor => 1, s => -1 } }; 40 | my $donut = NumberWithUnits(4, 'Hz', { newUnit => $Hurts }); 41 | my $cps = NumberWithUnits(4, 'cycles*s^-1'); 42 | 43 | my $todo = todo 'Will adding a newUnit overwrite the existing unit?'; 44 | is check_score($donut, $cps), 0, "We redefined the Hertz"; 45 | is check_score($donut, $inv_sec), 1, "Redefined as inverse seconds"; 46 | }; 47 | 48 | done_testing(); 49 | -------------------------------------------------------------------------------- /t/units/basic_module.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 '!E', { E => 'EXISTS' }; 4 | 5 | die "PG_ROOT not found in environment.\n" unless $ENV{PG_ROOT}; 6 | do "$ENV{PG_ROOT}/t/build_PG_envir.pl"; 7 | 8 | use lib "$ENV{PG_ROOT}/lib"; 9 | 10 | use Units qw(evaluate_units); 11 | 12 | # get unit hashes 13 | my %joule = evaluate_units('J'); 14 | my %newton_metre = evaluate_units('N*m'); 15 | my %energy_base_units = evaluate_units('kg*m^2/s^2'); 16 | 17 | # basic definitions of energy equivalence 18 | is \%joule, \%newton_metre, 'A joule is a newton-metre'; 19 | is \%joule, \%energy_base_units, 'A joule is a kg metre squared per second squared'; 20 | 21 | # test the error handling 22 | my $fake = 'bleurg'; 23 | ok my %error = evaluate_units($fake); 24 | like $error{ERROR}, qr/UNIT ERROR Unrecognizable unit: \|$fake\|/, "No unit '$fake' defined in Units file"; 25 | 26 | done_testing; 27 | -------------------------------------------------------------------------------- /t/units/electron_volts.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 '!E', { E => 'EXISTS' }; 4 | 5 | die "PG_ROOT not found in environment.\n" unless $ENV{PG_ROOT}; 6 | use lib "$ENV{PG_ROOT}/lib"; 7 | 8 | use Units qw(evaluate_units); 9 | 10 | my %joule = evaluate_units('J'); 11 | my %newton_metre = evaluate_units('N*m'); 12 | my %base_units = evaluate_units('kg*m^2/s^2'); 13 | 14 | my %electron_volt = evaluate_units('eV'); 15 | my %kev = evaluate_units('keV'); 16 | my %mev = evaluate_units('MeV'); 17 | my %gev = evaluate_units('GeV'); 18 | my %tev = evaluate_units('TeV'); 19 | 20 | is \%electron_volt, by_factor(1.6022E-19, \%joule), 'eV and joules differ by a factor of 1.6022 x 10^19'; 21 | is \%kev, by_factor(1000, \%electron_volt), 'kilo is factor 1000'; 22 | is \%mev, by_factor(10**6, \%electron_volt), 'mega is factor 10^6'; 23 | is \%gev, by_factor(10**9, \%electron_volt), 'giga is factor 10^9'; 24 | is \%tev, by_factor(10**12, \%electron_volt), 'tera is factor 10^12'; 25 | 26 | done_testing(); 27 | 28 | # this sub is useful when reusing units for testing 29 | # NumberWithUnits is mutable and test order dependant 30 | sub by_factor { 31 | my ($value, $unit) = @_; 32 | my $new_unit = {%$unit}; # shallow copy hash values 33 | 34 | $new_unit->{factor} *= $value; 35 | 36 | return $new_unit; 37 | } 38 | -------------------------------------------------------------------------------- /t/units/length.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 '!E', { E => 'EXISTS' }; 4 | 5 | die "PG_ROOT not found in environment.\n" unless $ENV{PG_ROOT}; 6 | do "$ENV{PG_ROOT}/t/build_PG_envir.pl"; 7 | 8 | use lib "$ENV{PG_ROOT}/lib"; 9 | 10 | use Units; 11 | use Parser::Legacy::NumberWithUnits; 12 | 13 | loadMacros('parserNumberWithUnits.pl'); 14 | 15 | my $micron = NumberWithUnits(1, 'um'); 16 | my $picometer = NumberWithUnits(1E6, 'pm'); 17 | my $femtometer = NumberWithUnits(1E9, 'fm'); 18 | my $angstrom = NumberWithUnits(1E4, 'angstrom'); 19 | 20 | subtest 'LaTeX output' => sub { 21 | is $picometer->TeX, '1\times 10^{6}\ {\rm pm}', 'LaTeX output for 1E6 picometers'; 22 | 23 | my $todo = todo 'Display units with greek mu'; 24 | is $micron->TeX, '1\ {\rm \mu m}', 'LaTeX output for micrometers'; 25 | }; 26 | 27 | subtest 'Equivalent to micrometer' => sub { 28 | is check_score($picometer, $micron), 1, '1 micrometer in picometers'; 29 | is check_score($femtometer, $micron), 1, '1 micrometer in femtometers'; 30 | is check_score($angstrom, $micron), 1, '1 micrometer in picometers'; 31 | }; 32 | 33 | done_testing(); 34 | -------------------------------------------------------------------------------- /t/units/radiation.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 '!E', { E => 'EXISTS' }; 4 | 5 | die "PG_ROOT not found in environment.\n" unless $ENV{PG_ROOT}; 6 | do "$ENV{PG_ROOT}/t/build_PG_envir.pl"; 7 | 8 | use lib "$ENV{PG_ROOT}/lib"; 9 | 10 | use Units; 11 | use Parser::Legacy::NumberWithUnits; 12 | 13 | loadMacros('parserNumberWithUnits.pl'); 14 | 15 | my $sievert = NumberWithUnits(1, 'Sv'); 16 | my $sievert_mSv = NumberWithUnits(1E3, 'mSv'); 17 | my $sievert_uSv = NumberWithUnits(1E6, 'uSv'); 18 | 19 | my $becquerel = NumberWithUnits(1, 'Bq'); 20 | my $reciprocal_second = NumberWithUnits(1, 's^-1'); 21 | 22 | subtest 'LaTeX output' => sub { 23 | is $sievert->TeX, '1\ {\rm Sv}', 'LaTeX output for 1 sievert'; 24 | 25 | my $todo = todo 'Display units with greek mu'; 26 | is $sievert_uSv->TeX, '1\times 10^{6}\ {\rm \mu Sv}', 'LaTeX output for microSieverts'; 27 | }; 28 | 29 | subtest 'Equivalent dose' => sub { 30 | is check_score($sievert_mSv, $sievert), 1, '1 Sv in mSv'; 31 | is check_score($sievert_uSv, $sievert), 1, '1 Sv in uSv'; 32 | }; 33 | 34 | subtest 'Radioactivity' => sub { 35 | is check_score($becquerel, $reciprocal_second), 1, 'a becquerel is a reciprocal second'; 36 | }; 37 | 38 | done_testing(); 39 | -------------------------------------------------------------------------------- /t/units/time.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 '!E', { E => 'EXISTS' }; 4 | 5 | die "PG_ROOT not found in environment.\n" unless $ENV{PG_ROOT}; 6 | do "$ENV{PG_ROOT}/t/build_PG_envir.pl"; 7 | 8 | use lib "$ENV{PG_ROOT}/lib"; 9 | 10 | use Units; 11 | use Parser::Legacy::NumberWithUnits; 12 | 13 | loadMacros('parserNumberWithUnits.pl'); 14 | 15 | my $second = NumberWithUnits(1, 's'); 16 | my $millisecond = NumberWithUnits(1, 'ms'); 17 | my $microsecond = NumberWithUnits(1, 'us'); 18 | my $nanosecond = NumberWithUnits(1, 'ns'); # used in optics 19 | 20 | my $min = NumberWithUnits(1, 'min'); 21 | my $hour = NumberWithUnits(1, 'hour'); 22 | my $day = NumberWithUnits(1, 'day'); 23 | my $year = NumberWithUnits(1, 'yr'); 24 | 25 | subtest 'LaTeX output' => sub { 26 | is $second->TeX, '1\ {\rm s}', 'LaTeX output for 1 second'; 27 | 28 | my $todo = todo 'Display units with greek mu'; 29 | is $microsecond->TeX, '1\ {\rm \mu s}', 'LaTeX output for 1 microsecond'; 30 | }; 31 | 32 | subtest 'Shorter times' => sub { 33 | is check_score($millisecond * Real(1000), $second), 1, 'a thousand millis in a second'; 34 | is check_score($microsecond * Real(1E6), $second), 1, 'a million micros in a second'; 35 | is check_score($nanosecond * Real(1E9), $second), 1, 'a billion nanos in a second'; 36 | }; 37 | 38 | subtest 'Longer times' => sub { 39 | is check_score($min / Real(60), $second), 1, '60 seconds in each minute run'; 40 | is check_score($hour / Real(3600), $second), 1, '60 minutes in an hour'; 41 | is check_score($day / Real(24 * 3600), $second), 1, '24 hours a day'; 42 | is check_score($year / Real(365.25 * 24 * 3600), $second), 1, 'an extra day every 4 years'; 43 | }; 44 | 45 | done_testing(); 46 | -------------------------------------------------------------------------------- /tutorial/css/sample-problem.css: -------------------------------------------------------------------------------- 1 | pre.CodeMirror { 2 | background-color: #fcfaf1; 3 | } 4 | .explanation { 5 | --bs-code-color: #971556; 6 | } 7 | .preamble { 8 | background-color: lightblue; 9 | } 10 | .setup { 11 | background-color: #ddffdd; 12 | } 13 | .statement { 14 | background-color: #eeb08199; 15 | } 16 | .answer { 17 | background-color: #ffffdd; 18 | } 19 | .solution { 20 | background-color: #ffb6c199; 21 | } 22 | .hint { 23 | background-color: rgb(239, 207, 251); 24 | } 25 | .perl { 26 | color: darkred; 27 | } 28 | -------------------------------------------------------------------------------- /tutorial/sample-problems/Algebra/AnswerUpToMultiplication.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Answers up to multiplication 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(PGML tutorial 2015) 8 | ## Date(06/01/2015) 9 | ## Institution(Hope College) 10 | ## Author(Paul Pearson) 11 | ## MO(1) 12 | ## KEYWORDS('algebra', 'answer up to multiplication') 13 | 14 | #:% name = Answer up to a Constant Multiple 15 | #:% type = Sample 16 | #:% subject = [algebra, precalculus] 17 | #:% categories = [answer, adaptive parameters] 18 | 19 | #:% section = preamble 20 | DOCUMENT(); 21 | 22 | loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); 23 | 24 | #:% section = setup 25 | #: The answer checker uses a local context with an adaptive parameter to check 26 | #: if the student answer is a parameter `C0` multiple of the correct answer. 27 | #: For more on adaptive parameters, see 28 | #: [AdaptiveParameters](../problem-techniques/AdaptiveParameters.html). 29 | 30 | $ans = Compute('(x + 1)(x - 2)')->cmp( 31 | checker => sub { 32 | my ($correct, $student, $self) = @_; 33 | return 0 if $student == 0; 34 | my $context = Context()->copy; 35 | $context->flags->set(no_parameters => 0); 36 | $context->variables->add('C0' => 'Parameter'); 37 | my $c0 = Formula($context, 'C0'); 38 | $student = Formula($context, $student); 39 | $correct = Formula($context, "$c0 * $correct"); 40 | return $correct == $student; 41 | } 42 | ); 43 | 44 | #:% section = statement 45 | BEGIN_PGML 46 | Find a quadratic equation in terms of the variable 47 | [`x`] with roots [`-1`] and [`2`]. 48 | 49 | [`y =`] [_]{$ans}{15} 50 | END_PGML 51 | 52 | #:% section = solution 53 | BEGIN_PGML_SOLUTION 54 | Solution explanation goes here. 55 | END_PGML_SOLUTION 56 | 57 | ENDDOCUMENT(); 58 | -------------------------------------------------------------------------------- /tutorial/sample-problems/Algebra/EquationDefiningFunction.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## An equation defining a function 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(PGML tutorial 2015) 8 | ## Date(06/01/2015) 9 | ## Institution(Hope College) 10 | ## Author(Paul Pearson) 11 | ## Static(1) 12 | ## MO(1) 13 | ## KEYWORDS('algebra', 'equation defining a function') 14 | 15 | #:% name = Answer is an Equation 16 | #:% type = Sample 17 | #:% subject = [algebra, precalculus] 18 | #:% categories = [equation, function, answers] 19 | 20 | #:% section = preamble 21 | #: We need to include the macro file `parserAssignment.pl`. 22 | DOCUMENT(); 23 | 24 | loadMacros('PGstandard.pl', 'PGML.pl', 'parserAssignment.pl', 'PGcourse.pl'); 25 | 26 | #:% section = setup 27 | #: We must allow assignment, and declare any function names we wish to use. For 28 | #: more details and examples in other MathObjects contexts, see 29 | #: PODLINK('parserAssignment.pl'). 30 | Context()->variables->are(x => 'Real', y => 'Real'); 31 | parser::Assignment->Allow; 32 | parser::Assignment->Function('f'); 33 | 34 | $eqn = Formula('y = 5x + 2'); 35 | $fun = Formula('f(x) = 3x^2 + 2x'); 36 | 37 | #:% section = statement 38 | BEGIN_PGML 39 | Enter [`[$eqn]`]: [_]{$eqn}{10} 40 | 41 | Enter [`[$fun]`]: [_]{$fun}{10} 42 | END_PGML 43 | 44 | #:% section = solution 45 | BEGIN_PGML_SOLUTION 46 | Solution explanation goes here. 47 | END_PGML_SOLUTION 48 | 49 | ENDDOCUMENT(); 50 | -------------------------------------------------------------------------------- /tutorial/sample-problems/Algebra/FractionAnswer.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Fraction answer 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(PGML tutorial 2015) 8 | ## Date(06/01/2015) 9 | ## Institution(Hope College) 10 | ## Author(Paul Pearson) 11 | ## MO(1) 12 | ## KEYWORDS('algebra', 'fraction answer') 13 | 14 | #:% name = Fraction Answer 15 | #:% type = Sample 16 | #:% subject = [algebra, precalculus] 17 | #:% categories = [fraction] 18 | 19 | #:% section = preamble 20 | #: The macro `contextFraction.pl` must be loaded. 21 | DOCUMENT(); 22 | 23 | loadMacros('PGstandard.pl', 'PGML.pl', 'contextFraction.pl', 'PGcourse.pl'); 24 | 25 | #:% section = setup 26 | #: The macro `contextFraction.pl` provides four contexts: 27 | #: 28 | #:```{#contexts .perl} 29 | #: Context('Fraction'); 30 | #: Context('Fraction-NoDecimals'); 31 | #: Context('LimitedFraction'); 32 | #: Context('LimitedProperFraction'); 33 | #:``` 34 | #: For the differences among these, see the POD documentation for 35 | #: PODLINK('contextFraction.pl'). 36 | Context('Fraction-NoDecimals'); 37 | 38 | $answer = Compute('3/2'); 39 | 40 | #:% section = statement 41 | #: There are many context flags that control how fraction answers are checked. 42 | #: See the POD documentation for PODLINK('contextFraction.pl'). 43 | BEGIN_PGML 44 | Simplify [``\frac{6}{4}``]. 45 | 46 | Answer = [_]{$answer->cmp( 47 | studentsMustReduceFractions => 1, 48 | reduceFractions => 1, 49 | allowMixedNumbers => 0 50 | )}{15} 51 | END_PGML 52 | 53 | #:% section = solution 54 | BEGIN_PGML_SOLUTION 55 | Factor and cancel to obtain [`\displaystyle [$answer]`]. 56 | END_PGML_SOLUTION 57 | 58 | ENDDOCUMENT(); 59 | -------------------------------------------------------------------------------- /tutorial/sample-problems/Algebra/InequalityAnswer.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Answer is an inequality 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(PGML tutorial 2015) 8 | ## Date(06/01/2015) 9 | ## Institution(Hope College) 10 | ## Author(Paul Pearson) 11 | ## MO(1) 12 | ## KEYWORDS('algebra', 'answer is an inequality') 13 | 14 | #:% name = Answer as an Inequality 15 | #:% type = Sample 16 | #:% subject = [algebra, precalculus] 17 | #:% categories = [fraction] 18 | 19 | #:% section = preamble 20 | #: We must load `contextInequalities.pl`. 21 | DOCUMENT(); 22 | 23 | loadMacros('PGstandard.pl', 'PGML.pl', 'contextInequalities.pl', 'PGcourse.pl'); 24 | 25 | #:% section = setup 26 | #: We require students to use inequalities by using `Context('Inequalities-Only')`. 27 | #: If we had used `Context('Inequalities')` instead, then students could also enter 28 | #: their answer using interval notation. For more details, please see 29 | #: PODLINK('contextInequalities.pl'). 30 | #: 31 | #: We use `formatStudentAnswer => 'parsed'` and `Compute()` so that the student's 32 | #: answer is left as a fraction rather than reduced to a decimal. 33 | Context('Inequalities-Only'); 34 | Context()->flags->set(formatStudentAnswer => 'parsed'); 35 | 36 | $a = random(3, 9); 37 | $ans = Compute("x >= -10 / $a"); 38 | 39 | #:% section = statement 40 | BEGIN_PGML 41 | Solve the inequality [``-[$a]x \leq 10``]. 42 | Enter your answer using inequality notation. 43 | 44 | [_]{$ans} 45 | 46 | [@ helpLink('inequalities') @]* 47 | END_PGML 48 | 49 | #:% section = solution 50 | BEGIN_PGML_SOLUTION 51 | Solution explanation goes here. 52 | END_PGML_SOLUTION 53 | 54 | ENDDOCUMENT(); 55 | -------------------------------------------------------------------------------- /tutorial/sample-problems/Algebra/LinearInequality.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Enter a linear inequality based on a description 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(PGML tutorial 2015) 8 | ## Date(06/01/2023) 9 | ## Institution(Fitchburg State University) 10 | ## Author(Peter Staab) 11 | ## MO(1) 12 | ## KEYWORDS('algebra', 'linear inequality') 13 | 14 | #:% name = Linear Inequality 15 | #:% type = Sample 16 | #:% subject = [algebra, precalculus] 17 | #:% categories = [inequality] 18 | 19 | #:% section = preamble 20 | #: We include the macro file `parserLinearRelation.pl` to be able to the a 21 | #: LinearRelation object. 22 | DOCUMENT(); 23 | 24 | loadMacros('PGstandard.pl', 'PGML.pl', 'parserLinearRelation.pl', 25 | 'PGcourse.pl'); 26 | 27 | #:% section = setup 28 | Context("LinearRelation"); 29 | 30 | do { 31 | $a = random(2, 6); 32 | $b = random(2, 6); 33 | } until ($a != $b); 34 | $ab = $a * $b; 35 | 36 | $lr = LinearRelation("$a x + $b y < $ab")->reduce; 37 | 38 | #:% section = statement 39 | #: Everything is as usual. Insert the fraction and answer blanks using `$showfraction`. 40 | BEGIN_PGML 41 | The line [`L`] that passes through the point [`([$b],0)`] and [`(0,[$a])`] divides 42 | the [`xy`]-plane. What is the linear relation that describes the set of 43 | points in half-plane containing the origin? Note, do not include the 44 | points on the line? 45 | 46 | [__]{$lr} 47 | 48 | END_PGML 49 | 50 | #:% section = solution 51 | BEGIN_PGML_SOLUTION 52 | [`[$lr]`] 53 | END_PGML_SOLUTION 54 | 55 | ENDDOCUMENT(); 56 | -------------------------------------------------------------------------------- /tutorial/sample-problems/Algebra/ScalingTranslating.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Scaling and translating functions 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(PGML tutorial 2015) 8 | ## Date(06/01/2015) 9 | ## Institution(Hope College) 10 | ## Author(Paul Pearson) 11 | ## MO(1) 12 | ## KEYWORDS('algebra', 'scaling and translating functions') 13 | 14 | #:% name = Scaling and Translating a Function 15 | #:% type = Sample 16 | #:% subject = [algebra, precalculus] 17 | #:% categories = [transformation] 18 | 19 | #:% section = preamble 20 | #: We must load `parserFunction.pl` so that we can add a named function to the context. 21 | DOCUMENT(); 22 | 23 | loadMacros('PGstandard.pl', 'PGML.pl', 'parserFunction.pl', 'PGcourse.pl'); 24 | 25 | #:% section = setup 26 | #: The `parserFunction` method allows us to add a named function to the context. We can 27 | #: define this function however we want, so we chose a function whose formula the 28 | #: students will not guess, whose domain is all real numbers, and which will have no 29 | #: issues during answer evaluation. Once a named function is added to the context, you 30 | #: can use it like you would any other named function. 31 | parserFunction(f => 'sin(e * x) + 5.5 * pi * x^2'); 32 | 33 | $answer = Formula('f(x - 2) + 1'); 34 | 35 | #:% section = statement 36 | BEGIN_PGML 37 | A function [`f(x)`] is shifted to the right [`2`] units and up [`1`] unit. Find 38 | a formula for this shifted function in terms of the function [`f(x)`]. 39 | 40 | [_]{$answer}{15} 41 | END_PGML 42 | 43 | #:% section = solution 44 | BEGIN_PGML_SOLUTION 45 | Solution explanation goes here. 46 | END_PGML_SOLUTION 47 | 48 | ENDDOCUMENT(); 49 | -------------------------------------------------------------------------------- /tutorial/sample-problems/Algebra/SolutionForEquation.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Answer is any solution to an equation 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(PGML tutorial 2015) 8 | ## Date(06/01/2015) 9 | ## Institution(Hope College) 10 | ## Author(Paul Pearson) 11 | ## MO(1) 12 | ## KEYWORDS('algebra', 'answer is any solution to an equation') 13 | 14 | #:% name = Solution for an Equation 15 | #:% type = Sample 16 | #:% subject = [algebra, precalculus] 17 | #:% categories = [algebra] 18 | 19 | #:% section = preamble 20 | #: The macro `parserSolutionFor.pl` must be loaded. 21 | DOCUMENT(); 22 | 23 | loadMacros('PGstandard.pl', 'PGML.pl', 'parserSolutionFor.pl', 'PGcourse.pl'); 24 | 25 | #:% section = setup 26 | #: We use `SolutionFor(equation, point)` to define this MathObject. For more 27 | #: details and options, see PODLINK('parserSolutionFor.pl'). 28 | Context('Vector'); 29 | 30 | $r = random(3, 6); 31 | $answer = SolutionFor("x^2 + y^2 + z^2 = $r^2", [ $r, 0, 0 ]); 32 | 33 | $eqn = $answer->{f}; 34 | 35 | #:% section = statement 36 | BEGIN_PGML 37 | A solution to the equation [`[$eqn]`] is [`(x, y, z) =`] [_]{$answer}{15} 38 | END_PGML 39 | 40 | #:% section = solution 41 | BEGIN_PGML_SOLUTION 42 | Solution explanation goes here. 43 | END_PGML_SOLUTION 44 | 45 | ENDDOCUMENT(); 46 | -------------------------------------------------------------------------------- /tutorial/sample-problems/Algebra/TableOfValues.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Function table of values 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(PGML tutorial 2015) 8 | ## Date(06/01/2015) 9 | ## Institution(Hope College) 10 | ## Author(Paul Pearson) 11 | ## MO(1) 12 | ## KEYWORDS('algebra', 'function table of values') 13 | 14 | #:% name = Table of Values 15 | #:% type = Sample 16 | #:% subject = [algebra, precalculus] 17 | #:% categories = [table] 18 | 19 | #:% section = preamble 20 | DOCUMENT(); 21 | 22 | loadMacros('PGstandard.pl', 'PGML.pl', 'niceTables.pl', 'PGcourse.pl'); 23 | 24 | #:% section = setup 25 | #: We create an empty array `@answer` and use a for loop to simplify filling it 26 | #: with values. 27 | #: 28 | #: The `DataTable` is from PODLINK('niceTables.pl'). This builds a simple table. The options 29 | #: `horizontalrules` and `texalignment` gives the borders around each of the cells. 30 | $f = Formula('3^(-x)'); 31 | 32 | @answer = (); 33 | for $i (0 .. 2) { 34 | $answer[$i] = $f->eval(x => $i); 35 | } 36 | 37 | $table = DataTable( 38 | [ 39 | [ '\(x\)', '\(f(x)\)' ], 40 | [ '\(0\)', ans_rule(4) ], 41 | [ '\(1\)', ans_rule(4) ], 42 | [ '\(2\)', ans_rule(4) ], 43 | ], 44 | horizontalrules => 1, 45 | texalignment => '|c|c|' 46 | ); 47 | 48 | #:% section = statement 49 | BEGIN_PGML 50 | If [`f(x) = [$f]`], fill in the table of values with numbers. 51 | 52 | [@ $table @]* 53 | END_PGML 54 | 55 | #:% section = answer 56 | #: Because the answer blanks are built with `ans_rule` inside the table, we 57 | #: need to use the traditional `ANS` call here. 58 | for $i (0 .. 2) { 59 | ANS($answer[$i]->cmp); 60 | } 61 | 62 | #:% section = solution 63 | BEGIN_PGML_SOLUTION 64 | Solution explanation goes here. 65 | END_PGML_SOLUTION 66 | 67 | ENDDOCUMENT(); 68 | -------------------------------------------------------------------------------- /tutorial/sample-problems/Algebra/UnorderedAnswers.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Answers can be entered in any order into answer blanks 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(PGML tutorial 2015) 8 | ## Date(06/01/2015) 9 | ## Institution(Hope College) 10 | ## Author(Paul Pearson) 11 | ## MO(1) 12 | ## KEYWORDS('algebra', 'answers can be entered in any order') 13 | 14 | #:% name = Unordered Answers 15 | #:% type = Sample 16 | #:% subject = [algebra, precalculus] 17 | #:% categories = [answer] 18 | 19 | #:% section = preamble 20 | #: The macro `unorderedAnswer.pl` must be loaded. 21 | DOCUMENT(); 22 | 23 | loadMacros('PGstandard.pl', 'PGML.pl', 'unorderedAnswer.pl', 'PGcourse.pl'); 24 | 25 | #:% section = setup 26 | #: Because the answers have the variables x, y, and z, add the latter two. 27 | Context()->variables->add(y => 'Real', z => 'Real'); 28 | 29 | $a = random(2, 9); 30 | 31 | $answer1 = Compute("x^$a"); 32 | $answer2 = Compute("y^$a"); 33 | $answer3 = Compute("z^$a"); 34 | 35 | #:% section = statement 36 | BEGIN_PGML 37 | Rewrite the following expression without parentheses. Simplify your answer as 38 | much as possible. 39 | 40 | [`(xyz)^{[$a]} =`] [_____] [`\cdot`] [_____] [`\cdot`] [_____] 41 | END_PGML 42 | 43 | #:% section = answer 44 | #: We use `UNORDERED_ANS(checker1, checker2, ...);` to evaluate the answers. It is 45 | #: possible to withhold feedback and credit until everything is correct by using 46 | #: the standard problem grader, which awards no partial credit and full credit 47 | #: only when everything is correct. 48 | $showPartialCorrectAnswers = 0; 49 | 50 | UNORDERED_ANS($answer1->cmp, $answer2->cmp, $answer3->cmp); 51 | 52 | #:% section = solution 53 | BEGIN_PGML_SOLUTION 54 | Solution explanation goes here. 55 | END_PGML_SOLUTION 56 | 57 | ENDDOCUMENT(); 58 | -------------------------------------------------------------------------------- /tutorial/sample-problems/DiffCalc/DifferenceQuotient.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Difference quotients 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(PGML tutorial 2015) 8 | ## Date(06/01/2015) 9 | ## Institution(Hope College) 10 | ## Author(Paul Pearson) 11 | ## Static(1) 12 | ## MO(1) 13 | ## KEYWORDS('differential calculus', 'difference quotients') 14 | 15 | #:% name = Difference Quotient 16 | #:% type = Sample 17 | #:% subject = differential calculus 18 | #:% categories = [difference quotient] 19 | 20 | #:% section = preamble 21 | #: We need to include the macros file `parserDifferenceQuotient.pl`. 22 | DOCUMENT(); 23 | 24 | loadMacros( 25 | 'PGstandard.pl', 'PGML.pl', 26 | 'parserDifferenceQuotient.pl', 'PGcourse.pl' 27 | ); 28 | 29 | #:% section = setup 30 | #: The routine DifferenceQuotient('function', 'variable') takes the simplified function 31 | #: and a variable name. If the variable is omitted, dx is used by default. 32 | $limit = DifferenceQuotient('2 * x + h', 'h'); 33 | 34 | $fp = Compute('2 x'); 35 | 36 | #:% section = statement 37 | BEGIN_PGML 38 | Simplify and then evaluate the limit. 39 | 40 | [`` 41 | \frac{d}{dx} \big( x^2 \big) 42 | = \lim_{h \to 0} \frac{(x+h)^2-x^2}{h} 43 | = \lim_{h \to 0} \Big( 44 | ``] [_]{$limit}{15} [``\Big) =``] [_]{$fp}{15} 45 | END_PGML 46 | 47 | #:% section = solution 48 | BEGIN_PGML_SOLUTION 49 | Solution explanation goes here. 50 | END_PGML_SOLUTION 51 | 52 | ENDDOCUMENT(); 53 | -------------------------------------------------------------------------------- /tutorial/sample-problems/Misc/FormulaAnswer.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Formula answer template 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(PGML tutorial 2015) 8 | ## Date(06/01/2015) 9 | ## Institution(Hope College) 10 | ## Author(Paul Pearson) 11 | ## MO(1) 12 | ## KEYWORDS('formula answer', 'template') 13 | 14 | #:% name = Formula Answer 15 | #:% type = Sample 16 | #:% subject = [algebra, precalculus] 17 | #:% categories = [answer] 18 | 19 | #:% section = preamble 20 | DOCUMENT(); 21 | 22 | loadMacros('PGstandard.pl', 'PGML.pl'); 23 | 24 | #:% section = setup 25 | #: Use `do { $b = random(2, 9) } until ( $b != $a );` to generate distinct 26 | #: random numbers. 27 | 28 | $a = non_zero_random(-9, 9); 29 | do { $b = random(2, 9) } until ($b != $a); 30 | 31 | $answer1 = Compute("$a"); 32 | $answer2 = Compute("($a x^($b) + $b)/x")->reduce(); 33 | 34 | #:% section = statement 35 | BEGIN_PGML 36 | Enter [`[$answer1]`]: [____]{$answer1} 37 | 38 | Enter [``[$answer2]``]: [____]{$answer2} 39 | END_PGML 40 | 41 | #:% section = solution 42 | BEGIN_PGML_SOLUTION 43 | Solution explanation goes here. 44 | END_PGML_SOLUTION 45 | 46 | ENDDOCUMENT(); 47 | -------------------------------------------------------------------------------- /tutorial/sample-problems/Misc/Scaffolding.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## scaffolding template 3 | ## ENDDESCRIPTION 4 | 5 | ## KEYWORDS('scaffold','scaffolding', 'sequentially revealed') 6 | 7 | ## DBsubject(WeBWorK) 8 | ## DBchapter(WeBWorK tutorial) 9 | ## DBsection(PGML tutorial 2015) 10 | ## Date(06/01/2015) 11 | ## Institution(Hope College) 12 | ## Author(Paul Pearson) 13 | 14 | #:% name = Scaffolded Problem 15 | #:% type = [Sample, technique] 16 | #:% categories = [misc] 17 | 18 | #:% section = preamble 19 | #: Make sure that the `scaffold.pl` macro is loaded. 20 | DOCUMENT(); 21 | 22 | loadMacros('PGstandard.pl', 'PGML.pl', 'scaffold.pl', 'PGcourse.pl'); 23 | 24 | #:% section = setup 25 | 26 | #:% section = statement 27 | #: Each `Section::Begin()` and `Section::End()` block can have its own context, etc. 28 | #: See the scaffold.pl macro file for Scaffold options. 29 | Scaffold::Begin(); 30 | 31 | Section::Begin('Part 1: The first part'); 32 | BEGIN_PGML 33 | This is the text for part 1. 34 | 35 | [`1 + 1 = `] [_]{2}{10} 36 | END_PGML 37 | Section::End(); 38 | 39 | Section::Begin('Part 2: The second part'); 40 | BEGIN_PGML 41 | This is text for the second part. 42 | 43 | [`2 * 2 = `] [_]{4}{10} 44 | END_PGML 45 | Section::End(); 46 | 47 | Section::Begin('Part 3: The third part'); 48 | BEGIN_PGML 49 | This is text for the third part. 50 | 51 | [`1 + 2 + 4 = `] [_]{7}{10} 52 | END_PGML 53 | Section::End(); 54 | 55 | Scaffold::End(); 56 | 57 | #:% section = solution 58 | BEGIN_PGML_SOLUTION 59 | Solution explanation goes here. 60 | END_PGML_SOLUTION 61 | 62 | ENDDOCUMENT(); 63 | -------------------------------------------------------------------------------- /tutorial/sample-problems/Parametric/SpaceCurveGraph.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Parametric equations: graphing a parametric curve in space 3 | ## ENDDESCRIPTION 4 | 5 | ## KEYWORDS('parametric', 'curve in space') 6 | 7 | ## DBsubject('WeBWorK') 8 | ## DBchapter(WeBWorK tutorial) 9 | ## DBsection(Fort Lewis tutorial 2011) 10 | ## Date('01/30/2011') 11 | ## Author('Paul Pearson') 12 | ## Institution('Fort Lewis College') 13 | 14 | #:% name = Space Curve Graph 15 | #:% type = Sample 16 | #:% subject = [parametric, graph] 17 | 18 | #:% section = preamble 19 | #: The macro `plotly3D.pl` is used to produce the graph. 20 | DOCUMENT(); 21 | 22 | loadMacros('PGstandard.pl', 'PGML.pl', 'plotly3D.pl', 'PGcourse.pl'); 23 | 24 | #:% section = setup 25 | #: A `plotly3D` graph is created with the `Graph3D` function. There are many options as 26 | #: decribed in PODLINK('the POD','plotly3D.pl'), but to get started include the `height` and `width`. 27 | #: 28 | #: A parametric curve (space curve) is added to the graph with the `addCurve` method, 29 | #: which takes an array ref of length 3. These are strings as javascript function in the 30 | #: variable `t`. The second array ref is `[tmin, tmax, samples]`. 31 | $graph = Graph3D( 32 | height => 300, 33 | width => 300, 34 | title => 'Spiral in 3D', 35 | ); 36 | 37 | $graph->addCurve([ 't*cos(t)', 't*sin(t)', 't' ], [ 0, 6 * pi, 150 ]); 38 | 39 | #:% section = statement 40 | #: This just prints the graph. No question is asked. 41 | BEGIN_PGML 42 | [@ $graph->Print @]* 43 | END_PGML 44 | 45 | ENDDOCUMENT(); 46 | -------------------------------------------------------------------------------- /tutorial/sample-problems/Parametric/SurfaceGraph.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Parametric equations: graphing a parametric curve in space 3 | ## ENDDESCRIPTION 4 | 5 | ## KEYWORDS('parametric', 'curve in space') 6 | 7 | ## DBsubject('WeBWorK') 8 | ## DBchapter(WeBWorK tutorial) 9 | ## DBsection(Fort Lewis tutorial 2011) 10 | ## Date('01/30/2011') 11 | ## Author('Paul Pearson') 12 | ## Institution('Fort Lewis College') 13 | 14 | #:% name = Surface Graph 15 | #:% type = Sample 16 | #:% subject = parametric 17 | 18 | #:% section = preamble 19 | #: The macro `plotly3D.pl` is used to produce the graph. 20 | DOCUMENT(); 21 | 22 | loadMacros('PGstandard.pl', 'PGML.pl', 'plotly3D.pl', 'PGcourse.pl'); 23 | 24 | #:% section = setup 25 | #: A `plotly3D` graph is created with the `Graph3D` function. There are many option 26 | #: (see PODLINK('the POD','plotly3D.pl')), but to get started include the `height` and `width`. 27 | #: 28 | #: A parametric surface is added to the graph with the `addSurface` method, 29 | #: which takes 3 array refs, each of length 3. 30 | #: 1. These are strings as javascript function in the variables `u` and `v`. 31 | #: 2. The parametric range in `u` or `[umin, umax, samples]`. 32 | #: 3. The parametric range in `v` or `[vmin, vmax, samples]`. 33 | $graph = Graph3D( 34 | height => 300, 35 | width => 300, 36 | title => 'Sphere', 37 | ); 38 | 39 | $graph->addSurface( 40 | [ '3*sin(v)*cos(u)', '3*sin(v)*sin(u)', '3*cos(v)' ], 41 | [ 0, 2 * pi, 30 ], 42 | [ 0, pi, 30 ] 43 | ); 44 | 45 | #:% section = statement 46 | #: This just prints the graph. No question is asked. 47 | BEGIN_PGML 48 | [@ $graph->Print @]* 49 | END_PGML 50 | 51 | ENDDOCUMENT(); 52 | -------------------------------------------------------------------------------- /tutorial/sample-problems/Sequences/AnswerOrderedList.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Answer is an ordered list 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(PGML tutorial 2015) 8 | ## Date(06/01/2015) 9 | ## Institution(Hope College) 10 | ## Author(Paul Pearson) 11 | ## MO(1) 12 | ## KEYWORDS('integral calculus', 'answer is an ordered list') 13 | 14 | #:% name = Ordered List 15 | #:% type = Sample 16 | #:% subject = Sequences and Series 17 | #:% categories = [sequences, answer] 18 | 19 | #:% section = preamble 20 | DOCUMENT(); 21 | 22 | loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); 23 | 24 | #:% section = setup 25 | #: We create the array `@seq` with the first two entries. The rest is filled 26 | #: with a `for` loop. Since the entries in the array `@seq` do not have commas between 27 | #: them, we create a Perl string `$answer` that joins the entries of the array `@seq` by 28 | #: a comma followed by a space ', '. Then, we make this string a MathObject by 29 | #: putting `Compute()` around it. 30 | #: 31 | #: Since the answer is a MathObject `List`, which is by default unordered, we must 32 | #: specify that the answer checker use `ordered=>1`. 33 | @seq = (1, 1); 34 | for $i (2 .. 6) { 35 | $seq[$i] = $seq[ $i - 1 ] + $seq[ $i - 2 ]; 36 | } 37 | 38 | $answer_cmp = Compute(join(', ', @seq))->cmp(ordered => 1); 39 | 40 | #:% section = statement 41 | BEGIN_PGML 42 | If [`s_1 = [$seq[0]]`], [`s_2 = [$seq[1]]`], and [`s_n = s_{n-1} + s_{n-2}`], 43 | find the first seven terms of this sequence, including [`s_1`] and [`s_2`]. 44 | Enter your answer as a comma separated list of numbers. 45 | 46 | Sequence = [_]{$answer_cmp}{20} 47 | END_PGML 48 | 49 | #:% section = solution 50 | BEGIN_PGML_SOLUTION 51 | Solution explanation goes here. 52 | END_PGML_SOLUTION 53 | 54 | ENDDOCUMENT(); 55 | -------------------------------------------------------------------------------- /tutorial/sample-problems/Trig/PeriodicAnswers.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Periodic answers 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(PGML tutorial 2015) 8 | ## Date(06/01/2015) 9 | ## Institution(Hope College) 10 | ## Author(Paul Pearson) 11 | ## MO(1) 12 | ## KEYWORDS('trigonometry', 'periodic answer') 13 | 14 | #:% name = Periodic Answers 15 | #:% type = Sample 16 | #:% subject = [trigonometry, precalculus] 17 | #:% categories = [trigonometry] 18 | 19 | #:% section = preamble 20 | DOCUMENT(); 21 | 22 | loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); 23 | 24 | #:% section = setup 25 | #: This allows any answer of the form `pi/2+n pi` for integer `n` to be 26 | #: accepted. 27 | $answer = Real('pi/2')->with(period => pi); 28 | 29 | #:% section = statement 30 | BEGIN_PGML 31 | Enter a solution to [`\cos(\theta) = 0`]. 32 | 33 | [`\theta =`] [_]{$answer}{15} 34 | END_PGML 35 | 36 | #:% section = solution 37 | BEGIN_PGML_SOLUTION 38 | The cosine of an angle is zero when the angle is [`(n + 1 / 2)\pi`] for any 39 | integer [`n`]. 40 | END_PGML_SOLUTION 41 | 42 | ENDDOCUMENT(); 43 | -------------------------------------------------------------------------------- /tutorial/sample-problems/Trig/TrigIdentities.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Trigonometric identities 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(PGML tutorial 2015) 8 | ## Date(06/01/2015) 9 | ## Institution(Hope College) 10 | ## Author(Paul Pearson) 11 | ## MO(1) 12 | ## KEYWORDS('trigonometry', 'trig identity') 13 | 14 | #:% name = Trigonometric Identities 15 | #:% type = Sample 16 | #:% subject = [trigonometry, precalculus] 17 | #:% categories = [trigonometry, custom] 18 | 19 | #:% section = preamble 20 | DOCUMENT(); 21 | 22 | loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); 23 | 24 | #:% section = setup 25 | #: To prevent the student from just entering the given expression, we create 26 | #: a custom answer checker, which 1) performs a `->reduce` which will do some 27 | #: small simplification, 2) returns an error if the original expression 28 | #: was put in and 3) then checks if the answer is correct. 29 | #: 30 | #: An alternative method to doing this is in 31 | #: PROBLINK('ProvingTrigIdentities.pg'). 32 | $ans = Compute('sin(x)')->cmp( 33 | checker => sub { 34 | my ($correct, $student, $ansHash) = @_; 35 | my $stu_ans = $student->reduce; 36 | Value->Error('There is a simpler answer') 37 | if $stu_ans->string eq 'cos(x)*tan(x)'; 38 | return $student == $correct; 39 | } 40 | ); 41 | 42 | #:% section = statement 43 | BEGIN_PGML 44 | Simplify the expression as much as possible. 45 | 46 | [`\tan(x) \cos(x) =`] [_]{$ans}{15} 47 | END_PGML 48 | 49 | #:% section = solution 50 | BEGIN_PGML_SOLUTION 51 | Solution explanation goes here. 52 | END_PGML_SOLUTION 53 | 54 | COMMENT( 55 | 'Prevents students from entering trivial identities (entering what they ' 56 | . 'were given)'); 57 | 58 | ENDDOCUMENT(); 59 | -------------------------------------------------------------------------------- /tutorial/sample-problems/VectorCalc/RectangularGraph3D.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## This shows an interactive graph in 3D in rectangular coordinates. 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(PGML tutorial 2015) 8 | ## Date(06/01/2015) 9 | ## Institution(Hope College) 10 | ## Author(Paul Pearson) 11 | ## MO(1) 12 | ## KEYWORDS('vector calculus', 'dynamic graph' , 'cylindrical') 13 | 14 | #:% name = Surface Graph in Rectangular Coordinates 15 | #:% type = Sample 16 | #:% subject = [Vector Calculus, multivariate calculus] 17 | #:% see_also = [SpaceCurveGraph.pg, SurfaceGraph.pg, CylindricalGraph3D.pg] 18 | #:% categories = [graph] 19 | 20 | #:% section = preamble 21 | #: The dynamic graph is generated with `plotly3D.pl`, 22 | #: so this is needed. 23 | DOCUMENT(); 24 | 25 | loadMacros('PGstandard.pl', 'PGML.pl', 'plotly3D.pl', 'PGcourse.pl'); 26 | 27 | #:% section=setup 28 | #: We generate the plot parametrically for the function `z=x^2+y^2` with the 29 | #: `addSurface` function. Since `x` and `y` are generally used with 30 | #: rectangular coordinates, we set them with the 31 | #:```{#vars .perl} 32 | #:variables => ['x','y'] 33 | #:``` 34 | #: 35 | #: See PODLINK('plotly3D.pl') for more information on options. 36 | 37 | $gr = Graph3D(); 38 | $gr->addSurface( 39 | [ 'x', 'y', 'x^2+y^2' ], 40 | [ -3, 3, 30 ], 41 | [ -3, 3, 30 ], 42 | variables => [ 'x', 'y' ] 43 | ); 44 | 45 | #:% section=statement 46 | #: This shows how to add a plot to the problem. 47 | BEGIN_PGML 48 | The following is the plot of [`z=x^2+y^2`] 49 | 50 | [@ $gr->Print @]* 51 | END_PGML 52 | 53 | #:% section=solution 54 | BEGIN_PGML_SOLUTION 55 | Solution explanation goes here. 56 | END_PGML_SOLUTION 57 | 58 | ENDDOCUMENT(); 59 | -------------------------------------------------------------------------------- /tutorial/sample-problems/VectorCalc/VectorFieldGraph3D/exploding-vector-field.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openwebwork/pg/afae348457c7a312e164d0fbc3947cf89242422f/tutorial/sample-problems/VectorCalc/VectorFieldGraph3D/exploding-vector-field.png -------------------------------------------------------------------------------- /tutorial/sample-problems/VectorCalc/VectorParametricLine.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## A Vector-value parametric line 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(Problem Techniques) 8 | ## Date(06/01/2008) 9 | ## Institution(University of Michigan) 10 | ## Author(Gavin LaRose) 11 | ## MO(1) 12 | ## KEYWORDS('interval') 13 | 14 | # created as a full problem by Peter Staab 2023.06.02 15 | 16 | #:% name = Vector-valued Parametric Line 17 | #:% type = [technique, sample] 18 | #:% categories = vector 19 | #:% subject = Vector Calculus 20 | 21 | #:% section = preamble 22 | #: The macro `parseVectorUtils.pl` provides random points. The macro 23 | #: `parseParametricLine.pl` provides the `ParametricLine` function which 24 | #: will be the answer. 25 | DOCUMENT(); 26 | loadMacros( 27 | 'PGstandard.pl', 'PGML.pl', 28 | 'parserVectorUtils.pl', 'parserParametricLine.pl', 29 | 'PGcourse.pl' 30 | ); 31 | 32 | #:% section = setup 33 | #: We randomize two points in three-dimensional space, `P` and `Q`, a 34 | #: displacement vector between them, and a speed to travel between them. 35 | Context('Vector'); 36 | Context()->variables->are(t => 'Real'); 37 | 38 | $P = non_zero_point3D(); 39 | $disp = non_zero_vector3D(); 40 | $Q = Point($P + $disp); 41 | $line = ParametricLine("$P+t *$disp"); 42 | 43 | #:% section = statement 44 | BEGIN_PGML 45 | Find a vector parametric equation for the 46 | line through points [` P = [$P] `] and [` Q = [$Q] `]. 47 | 48 | [` \vec{r}(t) = `] [___]{$line} 49 | 50 | A vector should be entered like . 51 | END_PGML 52 | 53 | #:% section = solution 54 | BEGIN_PGML_SOLUTION 55 | Solution explanation goes here. 56 | END_PGML_SOLUTION 57 | 58 | ENDDOCUMENT(); 59 | -------------------------------------------------------------------------------- /tutorial/sample-problems/problem-techniques/AddingFunctions.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Shows how to add a general function as an input option. 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(Problem Techniques) 8 | ## Date(06/01/2008) 9 | ## Institution(University of Michigan) 10 | ## Author(Gavin LaRose) 11 | ## MO(1) 12 | ## KEYWORDS('tolerance') 13 | 14 | # updated to full problem by Peter Staab (06/01/2023) 15 | 16 | #:% name = Adding Functions to a Context 17 | #:% type = technique 18 | #:% categories = [numbers, tolerance] 19 | 20 | #:% section = preamble 21 | #: We need to load the `parserFunction.pl` macro, and then use one of its 22 | #: routines to define a new function that students may type in their answers. 23 | DOCUMENT(); 24 | 25 | loadMacros('PGstandard.pl', 'PGML.pl', 'parserFunction.pl', 'PGcourse.pl'); 26 | 27 | #:% section = setup 28 | #: First, we define a function `f(x,y)`. The actual function doesn't matter but shouldn't 29 | #: be easily guessed, since a student could put in `sin(x*y)-exp(y)` and get the answer correct. 30 | #: 31 | #: This is a useful technique for any problem that the question is about a generic function 32 | #: rather than a specific one. 33 | Context()->variables->are(x => 'Real', y => 'Real'); 34 | parserFunction('f(x,y)' => 'sin(x*y)-exp(y)'); 35 | $ans = Compute('f(x-4,3)'); 36 | 37 | #:% section = statement 38 | BEGIN_PGML 39 | Given a surface [`z = f(x,y)`], what is the equation for the [`z`] -coordinate 40 | of the surface along a line having [`y=3`], shifted four units to the right? 41 | 42 | [`z = `] [_____]{$ans} 43 | END_PGML 44 | 45 | #:% section = solution 46 | BEGIN_PGML_SOLUTION 47 | Solution explanation goes here. 48 | END_PGML_SOLUTION 49 | 50 | ENDDOCUMENT(); 51 | -------------------------------------------------------------------------------- /tutorial/sample-problems/problem-techniques/AnswerInExponent.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Provides an answer blank in an exponent. 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(Problem Techniques) 8 | ## Date(06/01/2008) 9 | ## Institution(University of Michigan) 10 | ## Author(Gavin LaRose) 11 | ## MO(1) 12 | ## KEYWORDS('answer', 'exponent') 13 | 14 | # updated to full problem by Peter Staab (06/01/2023) 15 | 16 | #:% name = Answer in an Exponent 17 | #:% type = technique 18 | #:% categories = [answer, exponent] 19 | 20 | #:% section = preamble 21 | DOCUMENT(); 22 | 23 | loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); 24 | 25 | #:% section = setup 26 | #: The `Context()->variables->are` sets the variables for the context. 27 | Context()->variables->are(a => 'Real', b => 'Real'); 28 | 29 | $n = random(3, 9); 30 | 31 | $ans_rule1 = ans_rule(4); 32 | $ans_rule2 = ans_rule(4); 33 | 34 | #:% section = statement 35 | #: To create an exponential, there is different code for both the TeX (hardcopy) 36 | #: and HTML version. The `TeX` is as expected using the standard power `^`. 37 | #: The HTML version creates a exponent using the `vertical-align` attribute. 38 | BEGIN_PGML 39 | Rewrite the following using a single exponent. 40 | 41 | [@ MODES( 42 | TeX => "\\( \\displaystyle a^{$n}b^{$n} = ( $ans_rule1)^{$ans_rule2} \\)", 43 | HTML =>"
\\( \\displaystyle a^{$n}b^{$n} = (\\)$ans_rule1\\()\\)" . 44 | " $ans_rule2
" 45 | )@]* 46 | 47 | END_PGML 48 | 49 | #:% section = answer 50 | #: Because `ans_rule` is used above, this form of answer checking must be used. 51 | ANS(Compute('a b')->cmp); 52 | ANS(Real($n)->cmp); 53 | 54 | #:% section = solution 55 | BEGIN_PGML_SOLUTION 56 | Solution explanation goes here. 57 | END_PGML_SOLUTION 58 | 59 | ENDDOCUMENT(); 60 | -------------------------------------------------------------------------------- /tutorial/sample-problems/problem-techniques/AnswerIsSolutionToEquation.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## The answer is a solution to an equation. 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(Problem Techniques) 8 | ## Date(06/01/2008) 9 | ## Institution(University of Michigan) 10 | ## Author(Gavin LaRose) 11 | ## MO(1) 12 | ## KEYWORDS('answer', 'exponent') 13 | 14 | # updated to full problem by Peter Staab (06/01/2023) 15 | 16 | #:% name = Answer is a Solution to an Equation 17 | #:% type = technique 18 | #:% categories = [answer, exponent] 19 | 20 | #:% section = preamble 21 | #: We need to include the macros file `parserSolutionFor.pl` 22 | DOCUMENT(); 23 | 24 | loadMacros('PGstandard.pl', 'PGML.pl', 'parserSolutionFor.pl', 'PGcourse.pl'); 25 | 26 | #:% section = setup 27 | #: The function `SolutionFor('equation',point,options)` takes an equation, a point that 28 | #: satisfies that equation, and options such as `vars=>['y','x']` in case you want to change 29 | #: the order in which the variables appear in order pairs (the default is lexicographic ordering of the variables). 30 | Context('Vector')->variables->are(x => 'Real', y => 'Real'); 31 | 32 | $f = SolutionFor("x^2 = cos(y)", "(1,0)"); 33 | #$f = SolutionFor("x^2 - y = 0", [ 2, 4 ]); 34 | #$f = SolutionFor("x^2 - y = 0", Point(4, 2), vars => [ 'y', 'x' ]); 35 | 36 | #:% section = statement 37 | BEGIN_PGML 38 | A solution to [`[@ $f->{f} @]`] is [`(x,y) =`] [___]{$f} 39 | END_PGML 40 | 41 | #:% section = solution 42 | BEGIN_PGML_SOLUTION 43 | Solution explanation goes here. 44 | END_PGML_SOLUTION 45 | 46 | ENDDOCUMENT(); 47 | -------------------------------------------------------------------------------- /tutorial/sample-problems/problem-techniques/AnyAnswerMarkedCorrect.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Shows how to implement any answer marked correct. 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(Problem Techniques) 8 | ## Date(06/01/2008) 9 | ## Institution(University of Michigan) 10 | ## Author(Gavin LaRose) 11 | ## MO(1) 12 | ## KEYWORDS('answer') 13 | 14 | # updated to full problem by Peter Staab (06/01/2023) 15 | 16 | #:% name = Any Answer is Marked Correct 17 | #:% type = technique 18 | #:% categories = [answer] 19 | 20 | #:% section = preamble 21 | #: We need to include the macros file unionTables.pl 22 | DOCUMENT(); 23 | loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); 24 | 25 | #:% section=setup 26 | #: We wrap the random command with a `Compute` to make `$a` a MathObject. 27 | #: 28 | #: The checker then returns 1 which will make any answer correct. 29 | $a = Compute(random(2, 9, 1)); 30 | 31 | $ans = $a->cmp(checker => sub { return 1; }); 32 | 33 | #:% section = statement 34 | BEGIN_PGML 35 | Enter anything, e. g. [`[$a] `] and it will be marked correct: [__]{$ans} 36 | END_PGML 37 | 38 | #:% section = solution 39 | BEGIN_PGML_SOLUTION 40 | Solution explanation goes here. 41 | END_PGML_SOLUTION 42 | 43 | ENDDOCUMENT(); 44 | -------------------------------------------------------------------------------- /tutorial/sample-problems/problem-techniques/ComposingFunctions.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Shows how to check that the answer is the composition of two functions. 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(Problem Techniques) 8 | ## Date(06/01/2008) 9 | ## Institution(University of Michigan) 10 | ## Author(Gavin LaRose) 11 | ## MO(1) 12 | ## KEYWORDS('composition') 13 | 14 | # updated to full problem by Peter Staab (06/01/2023) 15 | 16 | #:% name = Composing Functions 17 | #:% type = technique 18 | #:% categories = [composition] 19 | 20 | #:% section = preamble 21 | #: We need to include the macros file `answerComposition.pl` 22 | DOCUMENT(); 23 | loadMacros('PGstandard.pl', 'PGML.pl', 'answerComposition.pl', 'PGcourse.pl'); 24 | 25 | #:% section = setup 26 | Context()->variables->are(x => 'Real', y => 'Real', u => 'Real'); 27 | 28 | $a = random(2, 9); 29 | 30 | $f = Formula("sqrt(u)"); 31 | $g = Formula("x^2+$a"); 32 | 33 | #:% section = statement 34 | BEGIN_PGML 35 | Express the function [`y = \sqrt{x^2 + [$a]}`] as a composition [`y = f(g(x))`] 36 | of two simpler functions [`y = f(u)`] and [`u = g(x)`]. 37 | 38 | [`f(u) =`] [___] 39 | 40 | [`g(x) =`] [___] 41 | END_PGML 42 | 43 | #:% section = answer 44 | #: This must be called with the method `COMPOSITION_ANS` with the arguments that 45 | #: will test for `f(g(x))` 46 | COMPOSITION_ANS($f, $g); 47 | 48 | #:% section = solution 49 | BEGIN_PGML_SOLUTION 50 | Solution explanation goes here. 51 | END_PGML_SOLUTION 52 | 53 | ENDDOCUMENT(); 54 | -------------------------------------------------------------------------------- /tutorial/sample-problems/problem-techniques/DifferentiatingFormulas.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## This shows how to check "arbitrary" conditions on the student's answer. 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(Problem Techniques) 8 | ## Date(06/01/2008) 9 | ## Institution(University of Michigan) 10 | ## Author(Gavin LaRose) 11 | ## MO(1) 12 | ## KEYWORDS('differentiate') 13 | 14 | # updated to full problem by Peter Staab (06/01/2023) 15 | 16 | #:% name = Differentiating Formulas 17 | #:% type = technique 18 | #:% categories = [derivative] 19 | 20 | #:% section = preamble 21 | DOCUMENT(); 22 | loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); 23 | 24 | #:% section=setup 25 | #: The `Numeric` context automatically defines `x` to be a variable, so we add the variable `y` 26 | #: to the context. Then, we use the partial differentiation operator `D('var_name') ` 27 | #: to take a partial derivative with respect to that variable. We can use the evaluate 28 | #: feature as expected. 29 | Context()->variables->add(y => "Real"); 30 | 31 | $a = random(2, 4, 1); 32 | $f = Formula("x*y^2"); 33 | 34 | $fx = $f->D('x'); 35 | $fxa = $fx->substitute(x => "$a"); 36 | $fy = $f->D('y'); 37 | $fyx = $fy->D('x')->reduce; 38 | 39 | #:% section=statement 40 | BEGIN_PGML 41 | Suppose [` f(x) = [$f] `]. Then 42 | 43 | a. [`` \frac{\partial f}{\partial x} ``] = [____]{$fx} 44 | 45 | b. [`f_x ([$a],y)= `] [____]{$fxa} 46 | 47 | c. [` f_y(x,y)=`] [____]{$fy} 48 | 49 | d. [`f_{yx} (x,y)= `] [___]{$fyx} 50 | 51 | END_PGML 52 | 53 | #:% section=solution 54 | BEGIN_PGML_SOLUTION 55 | Solution explanation goes here. 56 | END_PGML_SOLUTION 57 | 58 | ENDDOCUMENT(); 59 | -------------------------------------------------------------------------------- /tutorial/sample-problems/problem-techniques/EquationsDefiningFunctions.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## This PG code shows how to check student answers that are equations that 3 | ## define functions. If an equation defines a function, it is much more 4 | ## reliable to use the this method of answer evaluation (via parserAssignment.pl) 5 | ## than the implicit equation method (via parserImplicitEquation.pl). 6 | ## ENDDESCRIPTION 7 | 8 | ## DBsubject(WeBWorK) 9 | ## DBchapter(WeBWorK tutorial) 10 | ## DBsection(Problem Techniques) 11 | ## Date(06/01/2008) 12 | ## Institution(University of Michigan) 13 | ## Author(Gavin LaRose) 14 | ## MO(1) 15 | ## KEYWORDS('answer', 'custom') 16 | 17 | # updated to full problem by Peter Staab (06/01/2023) 18 | 19 | #:% name = Equations Defining Functions (Not Implicit) 20 | #:% type = technique 21 | #:% categories = [equation] 22 | 23 | #:% section = preamble 24 | #: In the initialization section, we need to include the macros file 25 | #: `parserAssignment.pl`. 26 | DOCUMENT(); 27 | loadMacros('PGstandard.pl', 'PGML.pl', 'parserAssignment.pl', 'PGcourse.pl'); 28 | 29 | #:% section=setup 30 | #: We must allow assignment, and declare any function names we wish to use. 31 | #: For more details and examples in other `MathObjects` contexts, see 32 | #: PODLINK('parserAssignment.pl'). 33 | Context("Numeric")->variables->are(x => "Real", y => "Real"); 34 | parser::Assignment->Allow; 35 | parser::Assignment->Function("f"); 36 | 37 | $eqn = Formula("y=5x+2"); 38 | $f = Formula("f(x)=5x+2"); 39 | 40 | #:% section=statement 41 | BEGIN_PGML 42 | Enter [`y = 5x+2`] [___]{$eqn} 43 | 44 | Enter [`f(x) = 5x+2`] [___]{$f} 45 | 46 | END_PGML 47 | 48 | #:% section=solution 49 | BEGIN_PGML_SOLUTION 50 | Solution explanation goes here. 51 | END_PGML_SOLUTION 52 | 53 | ENDDOCUMENT(); 54 | -------------------------------------------------------------------------------- /tutorial/sample-problems/problem-techniques/ErrorMessageCustomization.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## This shows how to customize (remap) the error messages students receive after submitting an 3 | ## incorrect response or making a syntax error when entering their answer. 4 | ## ENDDESCRIPTION 5 | 6 | ## DBsubject(WeBWorK) 7 | ## DBchapter(WeBWorK tutorial) 8 | ## DBsection(Problem Techniques) 9 | ## Date(06/01/2008) 10 | ## Institution(University of Michigan) 11 | ## Author(Gavin LaRose) 12 | ## MO(1) 13 | ## KEYWORDS('custom error') 14 | 15 | # updated to full problem by Peter Staab (06/01/2023) 16 | 17 | #:% name = Custom Error Message 18 | #:% type = technique 19 | #:% categories = [custom error] 20 | 21 | #:% section = preamble 22 | DOCUMENT(); 23 | loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); 24 | 25 | #:% section = setup 26 | #: To update the error message, the string in the `Context()->{error}{msg}` 27 | #: hash must match exactly and then is replaced with the customized 28 | #: version. 29 | Context()->{error}{msg}{"Missing operand after '-'"} = 30 | "Enter '-1' instead of '-'"; 31 | 32 | $ans1 = Real(-1); 33 | $ans2 = Formula("x-2"); 34 | 35 | #:% section = statement 36 | BEGIN_PGML 37 | Factor [`-1`] from [`-x+2`] 38 | 39 | [`-x+2 =`] [__]{$ans1} [`\cdot \big(`] [__]{$ans2} [`\big)`] 40 | END_PGML 41 | 42 | #:% section = solution 43 | BEGIN_PGML_SOLUTION 44 | Solution explanation goes here. 45 | END_PGML_SOLUTION 46 | 47 | ENDDOCUMENT(); 48 | -------------------------------------------------------------------------------- /tutorial/sample-problems/problem-techniques/IntervalEvaluators.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## This shows how to use intervals in a problem. 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(Problem Techniques) 8 | ## Date(06/01/2008) 9 | ## Institution(University of Michigan) 10 | ## Author(Gavin LaRose) 11 | ## MO(1) 12 | ## KEYWORDS('interval') 13 | 14 | # created as a full problem by Peter Staab 2023.06.02 15 | 16 | #:% name = Interval Evaluator 17 | #:% type = [technique] 18 | #:% categories = interval 19 | #:% subject = algebra 20 | #:% see_also = [InequalityEvaluators.pg] 21 | 22 | #:% section = preamble 23 | DOCUMENT(); 24 | loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); 25 | 26 | #:% section=setup 27 | #: In the problem set-up section of the file, we set the `Context` to be the 28 | #: `Interval` context. Note that we can relax checking of endpoints in the 29 | #: `Context` or in the actual answer checking, as noted below. 30 | #: 31 | #: Once we're in the `Interval` context, we can define intervals as we'd expect: 32 | #: as shown here, or with limits at infinity: 33 | #: 34 | #:```{#interval .perl} 35 | #: $int2 = Compute('(-inf,1]'); 36 | #:``` 37 | #: This would give the interval from negative infinity to 1, including 38 | #: the point at one. Note the Context flag to make endpoint checking "fuzzy." 39 | Context('Interval'); 40 | # to allow open or closed intervals, uncomment 41 | # the following line. 42 | # Context()->flags->set(ignoreEndpointTypes=>1); 43 | 44 | $int = Compute('(1,3)'); 45 | 46 | #:% section=statement 47 | BEGIN_PGML 48 | On what interval is the parabola [`y = (1-x)(x-3)`] 49 | above the [`x`]-axis? 50 | 51 | For [`x`] in [_____]{$int} 52 | END_PGML 53 | 54 | #:% section=solution 55 | BEGIN_PGML_SOLUTION 56 | Solution explanation goes here. 57 | END_PGML_SOLUTION 58 | 59 | ENDDOCUMENT(); 60 | -------------------------------------------------------------------------------- /tutorial/sample-problems/problem-techniques/Knowls.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## This shows how to use intervals in a problem. 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(Problem Techniques) 8 | ## Date(06/01/2008) 9 | ## Institution(University of Michigan) 10 | ## Author(Gavin LaRose) 11 | ## MO(1) 12 | ## KEYWORDS('interval') 13 | 14 | # created as a full problem by Peter Staab 2023.06.02 15 | 16 | #:% name = Knowls 17 | #:% type = [technique, snippet] 18 | 19 | #:% section = preamble 20 | DOCUMENT(); 21 | loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); 22 | 23 | #:% section=statement 24 | #: Knowls appear in the text section of the problem file. You can specify 25 | #: a value, as in the first example, which gives the text to appear in the 26 | #: Knowl, or the URL of a file with the HTML content for the knowl, as 27 | #: shown in the second example here. 28 | #: 29 | #: To include math text in the knowl, it is necessary to pipe the text 30 | #: through EV3P and escapeSolution HTML, as shown in the third example. 31 | BEGIN_PGML 32 | Here is a knowl 33 | [@ knowlLink("click me", value => 34 | 'This is the inside of a knowl. If you click again, I will go away') @]* 35 | 36 | Here is another knowl 37 | [@ knowlLink("click me", 38 | url=>'https://openwebwork.org') @]* 39 | 40 | 41 | [@ knowlLink("a math knowl", 42 | value=>escapeSolutionHTML(EV3P("the sine function is \\(\\frac{2}{3}\\)")), base64=>1); 43 | @]* 44 | END_PGML 45 | 46 | ENDDOCUMENT(); 47 | -------------------------------------------------------------------------------- /tutorial/sample-problems/problem-techniques/OtherVariables.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Add variables to the context. 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(Problem Techniques) 8 | ## Date(06/01/2008) 9 | ## Institution(University of Michigan) 10 | ## Author(Gavin LaRose) 11 | ## MO(1) 12 | ## KEYWORDS('variables') 13 | 14 | # updated to full problem by Peter Staab (06/01/2023) 15 | 16 | #:% name = Adding Variables to the Context 17 | #:% type = technique 18 | #:% categories = [variables] 19 | 20 | #:% section = preamble 21 | 22 | DOCUMENT(); 23 | loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); 24 | 25 | #:% section=setup 26 | #: Typically you either add or set the variables for the entire problem at the 27 | #: top of the setup section. In this case, we add y, z and t, all real numbers. 28 | #: 29 | #: This allows us to use a greek letter as a variable. Note that we have used 30 | #: add for this. If we set this with `are`, then the other variables will be 31 | #: deleted upon answer checked and the student will get a `variable not defined 32 | #: in this context` error. 33 | Context()->variables->add(t => 'Real', y => 'Real', z => 'Real'); 34 | $f = Compute('-16 t^2 + 5 t + 4'); 35 | $g = Compute('x^2+y^2+z^2'); 36 | 37 | Context()->variables->add(rho => [ 'Real', TeX => '\rho' ]); 38 | $h = Compute("sqrt(1+rho^2)"); 39 | 40 | #:% section=statement 41 | BEGIN_PGML 42 | Enter the following formulas: 43 | 44 | * [`[$f]=`] [____]{$f} 45 | * [`[$g]=`] [____]{$g} 46 | * [`[$h]=`] [____]{$h} 47 | END_PGML 48 | 49 | #:% section=solution 50 | BEGIN_PGML_SOLUTION 51 | Solution explanation goes here. 52 | END_PGML_SOLUTION 53 | 54 | ENDDOCUMENT(); 55 | -------------------------------------------------------------------------------- /tutorial/sample-problems/problem-techniques/Percent.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## This demonstrates the Percent context. 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(Problem Techniques) 8 | ## Date(06/01/2023) 9 | ## Institution(Fitchburg State University) 10 | ## Author(Peter Staab) 11 | ## MO(1) 12 | ## KEYWORDS('percent') 13 | 14 | #:% name = Percent Context 15 | #:% type = [technique] 16 | 17 | #:% section = preamble 18 | #: The `contextPercent.pl` must be loaded. 19 | DOCUMENT(); 20 | loadMacros('PGstandard.pl', 'PGML.pl', 'contextPercent.pl', 'PGcourse.pl'); 21 | 22 | #:% section = setup 23 | #: The `Percent` context must be loaded. 24 | Context('Percent'); 25 | 26 | $p = random(5, 95, 5); 27 | 28 | #:% section = statement 29 | #: The answer can be entered with a `%` or with the work `percent`. 30 | BEGIN_PGML 31 | Enter [$p]% [__]{Real($p)} 32 | END_PGML 33 | 34 | #:% section = solution 35 | BEGIN_PGML_SOLUTION 36 | Solution explanation goes here. 37 | END_PGML_SOLUTION 38 | 39 | ENDDOCUMENT(); 40 | -------------------------------------------------------------------------------- /tutorial/sample-problems/problem-techniques/RandomFunction.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## This demonstrates how to get a random function. 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(Problem Techniques) 8 | ## Date(06/01/2023) 9 | ## Institution(Fitchburg State University) 10 | ## Author(Peter Staab) 11 | ## MO(1) 12 | ## KEYWORDS('random function') 13 | 14 | #:% name = Random Function 15 | #:% type = [technique] 16 | 17 | #:% section = preamble 18 | DOCUMENT(); 19 | loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); 20 | 21 | #:% section = setup 22 | #: First, there are some random numbers generated as well as an array of 23 | #: functions using those values. The statement 24 | #: `random(0,$#funs)` generates a random number between 0 and (in this case 4, 25 | #: but in general 1 less than the length of the array) 26 | #: and then that element of the array is selected. 27 | # Define some random values and functions 28 | $a = non_zero_random(-8, 8); 29 | $b = random(1, 8); 30 | $n = random(2, 4); 31 | 32 | @funs = ( 33 | "1 + $a*x + $b x^2", 34 | "$a / (1 + $b x)", 35 | "$a x^3 + $b", 36 | "($a - x) / ($b + x^2)", 37 | "cos($n*x)" 38 | ); 39 | 40 | # This select one of the functions at random. 41 | $f = Formula($funs[ random(0, $#funs) ])->reduce; 42 | 43 | #:% section = statement 44 | BEGIN_PGML 45 | Enter [``[$f]``] [____]{$f} 46 | END_PGML 47 | 48 | #:% section = solution 49 | BEGIN_PGML_SOLUTION 50 | Solution explanation goes here. 51 | END_PGML_SOLUTION 52 | 53 | ENDDOCUMENT(); 54 | -------------------------------------------------------------------------------- /tutorial/sample-problems/problem-techniques/RestrictingFunctions.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Restricting answers that should reduce to a constant. 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(PGML tutorial 2015) 8 | ## Date(06/01/2015) 9 | ## Institution(Fitchburg State University) 10 | ## Author(Peter Staab) 11 | ## MO(1) 12 | ## KEYWORDS('answer', 'constant') 13 | 14 | #:% name = Restrict Answers to a Constant 15 | #:% type = [technique, sample] 16 | #:% subject = [answer] 17 | #:% see_also = [RestrictAnswerToFraction.pg] 18 | 19 | #:% section = preamble 20 | DOCUMENT(); 21 | 22 | loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); 23 | 24 | #:% section = setup 25 | #: Here we've turned off type warnings in the answer checking, so that a 26 | #: student entering an un-simplified answer (e.g., 27 | #: `2 sin(x) cos(x) + 2 cos(x) (-sin(x))`) will have it marked wrong 28 | #: (but not get feedback that says "you should have entered a number"). 29 | $expr = Formula("sin(x)^2 + cos(x)^2"); 30 | $deriv = Compute(0)->cmp(showTypeWarnings => 0); 31 | 32 | #:% section = statement 33 | BEGIN_PGML 34 | Find and completely simplify: 35 | 36 | [``\frac{d}{dx}\bigl(\sin^2 x + \cos^2 x\bigr) = ``] [__]{$deriv} 37 | END_PGML 38 | 39 | #:% section = solution 40 | BEGIN_PGML_SOLUTION 41 | Solution explanation goes here. 42 | END_PGML_SOLUTION 43 | 44 | ENDDOCUMENT(); 45 | -------------------------------------------------------------------------------- /tutorial/sample-problems/problem-techniques/WeightedGrader.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## This shows how to use intervals in a problem. 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(Problem Techniques) 8 | ## Date(06/01/2008) 9 | ## Institution(University of Michigan) 10 | ## Author(Gavin LaRose) 11 | ## MO(1) 12 | ## KEYWORDS('weighted grader') 13 | 14 | # created as a full problem by Peter Staab 2023.06.02 15 | 16 | #:% name = Weighted Grader 17 | #:% type = [technique] 18 | #:% categories = [grader] 19 | 20 | #:% section = preamble 21 | DOCUMENT(); 22 | loadMacros('PGstandard.pl', 'PGML.pl', 'weightedGrader.pl', 'PGcourse.pl'); 23 | 24 | #:% section=setup 25 | #: Call `install_weighted_grader();` so that the weighted grader is used. 26 | install_weighted_grader(); 27 | 28 | #:% section=statement 29 | #: Assign weights to answers by passing the `weight` via `cmp_options`. The 30 | #: example here gives weights as percents that sum to 100, but weights of 31 | #: (2, 5, 3), (4, 10, 6), or (0.2, 0.5, 0.3) would give the same weighting. 32 | BEGIN_PGML 33 | * This answer is worth 20%. Enter 1 [___]{1}{ cmp_options => { weight => 20 } } 34 | 35 | * This answer is worth 50%. Enter 3 [___]{3}{ cmp_options => { weight => 50 } } 36 | 37 | * This answer is worth 30%. Enter 7 [___]{7}{ cmp_options => { weight => 30 } } 38 | END_PGML 39 | 40 | #:% section=solution 41 | BEGIN_PGML_SOLUTION 42 | Solution explanation goes here. 43 | END_PGML_SOLUTION 44 | 45 | ENDDOCUMENT(); 46 | -------------------------------------------------------------------------------- /tutorial/sample-problems/problem-techniques/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openwebwork/pg/afae348457c7a312e164d0fbc3947cf89242422f/tutorial/sample-problems/problem-techniques/image.png -------------------------------------------------------------------------------- /tutorial/sample-problems/problem-techniques/local.html: -------------------------------------------------------------------------------- 1 | 2 |

Local File

3 |

This is an example of a local html file.

4 | -------------------------------------------------------------------------------- /tutorial/sample-problems/snippets/CommentsForInstructors.pg: -------------------------------------------------------------------------------- 1 | ## DESCRIPTION 2 | ## Shows a Comment 3 | ## ENDDESCRIPTION 4 | 5 | ## DBsubject(WeBWorK) 6 | ## DBchapter(WeBWorK tutorial) 7 | ## DBsection(Problem Techniques) 8 | ## Date(06/01/2008) 9 | ## Institution(University of Michigan) 10 | ## Author(Gavin LaRose) 11 | ## MO(1) 12 | ## KEYWORDS('snippet', 'comment') 13 | 14 | # updated to full problem by Peter Staab (06/01/2023) 15 | 16 | #:% name = Comment for Instructors 17 | #:% type = snippet 18 | 19 | DOCUMENT(); 20 | 21 | loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); 22 | 23 | #:% section = comment 24 | #: Include the `COMMENT();` just before the `ENDDOCUMENT();` Comments are only visible 25 | #: when the PG file is viewed in the Library Browser, so students will not see them. 26 | COMMENT('This problem is not randomized.'); 27 | 28 | ENDDOCUMENT(); 29 | -------------------------------------------------------------------------------- /tutorial/templates/general-main.mt: -------------------------------------------------------------------------------- 1 | % if ($type eq 'techniques') { 2 | % for (['A' .. 'C'], ['D' .. 'F'], ['G' .. 'N'], ['O' .. 'Z']) { 3 |
4 |

Sample Problems for Techniques: <%= $_->[0] %> .. <%= $_->[-1] %>

5 |
    6 | % my $b = join('', @$_); 7 | % for (sort grep { substr($_, 0, 1 ) =~ qr/^[$b]/i } keys(%$list)) { 8 |
  • <%= $_ =%>
  • 9 | % } 10 |
11 |
12 | % } 13 | % } else { 14 | % for (sort(keys %$list)) { 15 | % my %topics = (categories => 'Catetory', subjects => 'Subject', macros => 'Macro'); 16 | % my $id = $_ =~ s/\s/_/gr; 17 |
19 |

Sample Problems for <%= $topics{$type} %>: <%= $_ %>

20 |
    21 | % for my $link (sort (keys %{$list->{$_}})) { 22 |
  • <%= $link %>
  • 23 | % } 24 |
25 |
26 | % } 27 | % } 28 | -------------------------------------------------------------------------------- /tutorial/templates/general-sidebar.mt: -------------------------------------------------------------------------------- 1 |
2 | 3 | 6 |
7 | 8 |

<%= $label =%>

9 |
10 | 28 |
29 | --------------------------------------------------------------------------------