9.3: Macro Demonstrations
- Page ID
- 557639
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\( \newcommand{\dsum}{\displaystyle\sum\limits} \)
\( \newcommand{\dint}{\displaystyle\int\limits} \)
\( \newcommand{\dlim}{\displaystyle\lim\limits} \)
\( \newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\)
( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\id}{\mathrm{id}}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\kernel}{\mathrm{null}\,}\)
\( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\)
\( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\)
\( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\AA}{\unicode[.8,0]{x212B}}\)
\( \newcommand{\vectorA}[1]{\vec{#1}} % arrow\)
\( \newcommand{\vectorAt}[1]{\vec{\text{#1}}} % arrow\)
\( \newcommand{\vectorB}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vectorC}[1]{\textbf{#1}} \)
\( \newcommand{\vectorD}[1]{\overrightarrow{#1}} \)
\( \newcommand{\vectorDt}[1]{\overrightarrow{\text{#1}}} \)
\( \newcommand{\vectE}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash{\mathbf {#1}}}} \)
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\(\newcommand{\longvect}{\overrightarrow}\)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\(\newcommand{\avec}{\mathbf a}\) \(\newcommand{\bvec}{\mathbf b}\) \(\newcommand{\cvec}{\mathbf c}\) \(\newcommand{\dvec}{\mathbf d}\) \(\newcommand{\dtil}{\widetilde{\mathbf d}}\) \(\newcommand{\evec}{\mathbf e}\) \(\newcommand{\fvec}{\mathbf f}\) \(\newcommand{\nvec}{\mathbf n}\) \(\newcommand{\pvec}{\mathbf p}\) \(\newcommand{\qvec}{\mathbf q}\) \(\newcommand{\svec}{\mathbf s}\) \(\newcommand{\tvec}{\mathbf t}\) \(\newcommand{\uvec}{\mathbf u}\) \(\newcommand{\vvec}{\mathbf v}\) \(\newcommand{\wvec}{\mathbf w}\) \(\newcommand{\xvec}{\mathbf x}\) \(\newcommand{\yvec}{\mathbf y}\) \(\newcommand{\zvec}{\mathbf z}\) \(\newcommand{\rvec}{\mathbf r}\) \(\newcommand{\mvec}{\mathbf m}\) \(\newcommand{\zerovec}{\mathbf 0}\) \(\newcommand{\onevec}{\mathbf 1}\) \(\newcommand{\real}{\mathbb R}\) \(\newcommand{\twovec}[2]{\left[\begin{array}{r}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\ctwovec}[2]{\left[\begin{array}{c}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\threevec}[3]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\cthreevec}[3]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\fourvec}[4]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\cfourvec}[4]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\fivevec}[5]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\cfivevec}[5]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\mattwo}[4]{\left[\begin{array}{rr}#1 \amp #2 \\ #3 \amp #4 \\ \end{array}\right]}\) \(\newcommand{\laspan}[1]{\text{Span}\{#1\}}\) \(\newcommand{\bcal}{\cal B}\) \(\newcommand{\ccal}{\cal C}\) \(\newcommand{\scal}{\cal S}\) \(\newcommand{\wcal}{\cal W}\) \(\newcommand{\ecal}{\cal E}\) \(\newcommand{\coords}[2]{\left\{#1\right\}_{#2}}\) \(\newcommand{\gray}[1]{\color{gray}{#1}}\) \(\newcommand{\lgray}[1]{\color{lightgray}{#1}}\) \(\newcommand{\rank}{\operatorname{rank}}\) \(\newcommand{\row}{\text{Row}}\) \(\newcommand{\col}{\text{Col}}\) \(\renewcommand{\row}{\text{Row}}\) \(\newcommand{\nul}{\text{Nul}}\) \(\newcommand{\var}{\text{Var}}\) \(\newcommand{\corr}{\text{corr}}\) \(\newcommand{\len}[1]{\left|#1\right|}\) \(\newcommand{\bbar}{\overline{\bvec}}\) \(\newcommand{\bhat}{\widehat{\bvec}}\) \(\newcommand{\bperp}{\bvec^\perp}\) \(\newcommand{\xhat}{\widehat{\xvec}}\) \(\newcommand{\vhat}{\widehat{\vvec}}\) \(\newcommand{\uhat}{\widehat{\uvec}}\) \(\newcommand{\what}{\widehat{\wvec}}\) \(\newcommand{\Sighat}{\widehat{\Sigma}}\) \(\newcommand{\lt}{<}\) \(\newcommand{\gt}{>}\) \(\newcommand{\amp}{&}\) \(\definecolor{fillinmathshade}{gray}{0.9}\)This appendix provides one complete, renderable PG problem for each of the most commonly used macros documented in Common PG Macros. Every demo is a full DOCUMENT()/ENDDOCUMENT() problem with a biology-themed prompt that you can copy, paste, and render immediately.
Some macros already have complete working examples elsewhere in the textbook. Where that is the case, a cross-reference replaces the demo. Each entry also includes a brief version history note so you know how long the macro has been available and what changed in recent PG releases.
Interaction widgets
parserRadioButtons.pl
History: Long-running (MathObjects era). Values display added PG 2.18 (2023).
Complete examples appear in Multiple Choice (multiple choice) and Minimal Templates. See those pages for copy-paste RadioButtons demos.
parserPopUp.pl
History: Long-running (MathObjects era). DropDown method added PG 2.18. Breaking change PG 2.19.
Complete examples appear in Matching (matching) and Minimal Templates. See those pages for copy-paste PopUp demos.
PGchoicemacros.pl
History: Long-running legacy macro (WeBWorK 2.0+, 2004). Predates MathObjects.
Note: PGchoicemacros.pl is a legacy macro. Its utility functions (NchooseK, shuffle) are deprecated; PGsort is a core function from PGbasicmacros.pl, not from this macro. The non-deprecated core is new_match_list with print_q/print_a. These methods generate their own form fields, so ANS() is required. For new problems, prefer PopUp (parserPopUp.pl) or RadioButtons (parserRadioButtons.pl).
## TITLE('Match organelles to functions')
## DESCRIPTION
## Use PGchoicemacros new_match_list to associate organelles
## with their primary cellular functions.
## ENDDESCRIPTION
## KEYWORDS('matching','organelles','match list')
## DBsubject('Cell Biology')
## DBchapter('Organelles')
## DBsection('Functions')
## Date('2026-02-16')
## Author('Textbook demo')
## Institution('LibreTexts')
DOCUMENT();
loadMacros(
"PGstandard.pl",
"PGML.pl",
"PGchoicemacros.pl",
"PGcourse.pl",
);
$ml = new_match_list();
$ml->qa(
"Site of aerobic respiration", "Mitochondrion",
"Site of protein synthesis", "Ribosome",
"Packages and ships proteins", "Golgi apparatus",
"Contains the cell's DNA", "Nucleus",
);
$ml->choose(4);
BEGIN_PGML
Match each function with the correct organelle.
[@ $ml->print_q @]*
[@ $ml->print_a @]*
END_PGML
ANS(str_cmp($ml->ra_correct_ans));
ENDDOCUMENT();
draggableProof.pl
History: Introduced ~PG 2.14. Updated PG 2.17 (2022) for Gateway quiz support.
Complete examples appear in Ordered List (ordered list) and Minimal Templates. See those pages for copy-paste DraggableProof demos.
draggableSubsets.pl
History: Introduced ~PG 2.14.
## TITLE('Sort molecules into macromolecule categories')
## DESCRIPTION
## Drag molecules into the correct macromolecule bin using
## DraggableSubsets.
## ENDDESCRIPTION
## KEYWORDS('classification','macromolecules','drag and drop')
## DBsubject('Biochemistry')
## DBchapter('Macromolecules')
## DBsection('Classification')
## Date('2026-02-15')
## Author('Textbook demo')
## Institution('LibreTexts')
DOCUMENT();
loadMacros(
"PGstandard.pl",
"MathObjects.pl",
"PGML.pl",
"draggableSubsets.pl",
"PGcourse.pl",
);
$draggable = DraggableSubsets(
[ "Glucose", "Starch", "Hemoglobin", "Phospholipid",
"Cellulose", "Insulin", "Cholesterol", "Glycogen" ],
[
[ 0, 1, 4, 7 ],
[ 2, 5 ],
[ 3, 6 ],
],
DefaultSubsets => [
{ label => "Carbohydrates", indices => [] },
{ label => "Proteins", indices => [] },
{ label => "Lipids", indices => [] },
],
);
BEGIN_PGML
Drag each molecule into the correct macromolecule category.
[_]{$draggable}
END_PGML
ENDDOCUMENT();
parserWordCompletion.pl
History: Long-running (MathObjects era).
## TITLE('Fill in homeostasis from a word bank')
## DESCRIPTION
## Select the correct biological term from a word bank using
## WordCompletion.
## ENDDESCRIPTION
## KEYWORDS('word bank','homeostasis','terminology')
## DBsubject('Physiology')
## DBchapter('Homeostasis')
## DBsection('Core concepts')
## Date('2026-02-15')
## Author('Textbook demo')
## Institution('LibreTexts')
DOCUMENT();
loadMacros(
"PGstandard.pl",
"MathObjects.pl",
"PGML.pl",
"parserWordCompletion.pl",
"PGcourse.pl",
);
Context("Numeric");
$wc = WordCompletion(
[ "homeostasis", "metabolism", "mitosis", "osmosis", "diffusion" ],
"homeostasis",
);
BEGIN_PGML
The process by which organisms maintain a stable internal environment
despite changes in external conditions is called
[_]{$wc}.
_Word bank: homeostasis, metabolism, mitosis, osmosis, diffusion_
END_PGML
ENDDOCUMENT();
PGessaymacros.pl
History: Introduced ~PG 2.9 (2014). Equation editor added PG 2.16.
Note: Essay questions cannot be auto-graded. The student sees a text box and the instructor must grade manually. The essay_box() function creates its own form field, so ANS(essay_cmp()) is required after END_PGML.
## TITLE('Short essay on natural selection')
## DESCRIPTION
## Prompt students to write a short explanation of natural
## selection using an essay box.
## ENDDESCRIPTION
## KEYWORDS('essay','natural selection','free response')
## DBsubject('Evolution')
## DBchapter('Natural Selection')
## DBsection('Mechanisms')
## Date('2026-02-15')
## Author('Textbook demo')
## Institution('LibreTexts')
DOCUMENT();
loadMacros(
"PGstandard.pl",
"PGessaymacros.pl",
"PGML.pl",
"PGcourse.pl",
);
BEGIN_PGML
In three to five sentences, explain how natural selection
leads to adaptation in a population over time. Include at
least one specific example from a real organism.
[@ essay_box(8, 60) @]*
END_PGML
ANS(essay_cmp());
ENDDOCUMENT();
Science-native contexts
parserNumberWithUnits.pl
History: Long-running (MathObjects era). Unit improvements PG 2.18.
## TITLE('Enzyme reaction rate with units')
## DESCRIPTION
## Calculate an enzyme reaction rate and report the answer
## with correct units using NumberWithUnits.
## ENDDESCRIPTION
## KEYWORDS('units','enzyme kinetics','reaction rate')
## DBsubject('Biochemistry')
## DBchapter('Enzyme Kinetics')
## DBsection('Reaction rates')
## Date('2026-02-15')
## Author('Textbook demo')
## Institution('LibreTexts')
DOCUMENT();
loadMacros(
"PGstandard.pl",
"MathObjects.pl",
"PGML.pl",
"parserNumberWithUnits.pl",
"PGcourse.pl",
);
Context("Numeric");
$moles = random(2, 8, 1);
$seconds = random(10, 30, 5);
$rate_val = $moles / $seconds;
$ans = NumberWithUnits("$rate_val mol/s");
BEGIN_PGML
An enzyme converts [$moles] mol of substrate in [$seconds] seconds.
What is the reaction rate? Include units in your answer.
Rate = [__________]{$ans}
_(Enter your answer as a number followed by units, e.g. "0.5 mol/s")_
END_PGML
ENDDOCUMENT();
parserFormulaWithUnits.pl
History: Long-running (MathObjects era).
## TITLE('Dilution formula with units')
## DESCRIPTION
## Express a dilution formula C1*V1/V2 with units using
## FormulaWithUnits.
## ENDDESCRIPTION
## KEYWORDS('formula','dilution','units')
## DBsubject('Biochemistry')
## DBchapter('Solutions')
## DBsection('Dilutions')
## Date('2026-02-15')
## Author('Textbook demo')
## Institution('LibreTexts')
DOCUMENT();
loadMacros(
"PGstandard.pl",
"MathObjects.pl",
"PGML.pl",
"parserFormulaWithUnits.pl",
"PGcourse.pl",
);
Context("Numeric");
Context()->variables->add(c => "Real");
$ans = FormulaWithUnits("3 c ml");
BEGIN_PGML
A formula for the volume of a solution used in a serial
dilution is [`V = 3c`] mL, where [`c`] is a
concentration factor.
Express this formula with units.
[`V`] = [__________]{$ans}
_(Enter a formula with units, e.g. "3 c ml")_
END_PGML
ENDDOCUMENT();
contextScientificNotation.pl
History: Long-running (MathObjects era).
## TITLE('Bacterial count after doublings')
## DESCRIPTION
## Calculate the number of bacteria after several doublings
## and express the answer in scientific notation.
## ENDDESCRIPTION
## KEYWORDS('scientific notation','bacteria','exponential growth')
## DBsubject('Microbiology')
## DBchapter('Growth')
## DBsection('Exponential growth')
## Date('2026-02-15')
## Author('Textbook demo')
## Institution('LibreTexts')
DOCUMENT();
loadMacros(
"PGstandard.pl",
"MathObjects.pl",
"PGML.pl",
"contextScientificNotation.pl",
"PGcourse.pl",
);
Context("ScientificNotation");
$doublings = random(10, 20, 1);
$initial = 1000;
$final = $initial * 2**$doublings;
$ans = ScientificNotation($final);
BEGIN_PGML
A bacterial culture starts with [$initial] cells and doubles
every 30 minutes. After [$doublings] doublings, how many
cells are present?
Express your answer in scientific notation.
Number of cells = [______________]{$ans}
_(Example format: 1.5 x 10^6 or 1.5E6)_
END_PGML
ENDDOCUMENT();
contextReaction.pl
History: Long-running (MathObjects era). Improved PG 2.20 (2025).
## TITLE('Write a combustion reaction')
## DESCRIPTION
## Enter a balanced combustion reaction using the
## Reaction context.
## ENDDESCRIPTION
## KEYWORDS('chemical equation','combustion','reaction')
## DBsubject('Biochemistry')
## DBchapter('Metabolism')
## DBsection('Chemical reactions')
## Date('2026-02-15')
## Author('Textbook demo')
## Institution('LibreTexts')
DOCUMENT();
loadMacros(
"PGstandard.pl",
"MathObjects.pl",
"PGML.pl",
"contextReaction.pl",
"PGcourse.pl",
);
Context("Reaction");
$rxn = Formula("2 H_2 + O_2 --> 2 H_2O");
BEGIN_PGML
Hydrogen gas reacts with oxygen gas to produce water.
Write the balanced equation for this reaction.
Use --> for the reaction arrow. Use _ for subscripts
(e.g., H_2 for [`\text{H}_2`]).
Equation: [__________________________]{$rxn}
_(Example format: 2 H_2 + O_2 --> 2 H_2O)_
END_PGML
ENDDOCUMENT();
contextFraction.pl
History: Long-running (MathObjects era). num/den methods added PG 2.19 (2024). Updated PG 2.20.
## TITLE('Mendelian cross offspring ratio')
## DESCRIPTION
## Calculate the fraction of offspring with a recessive
## phenotype from a monohybrid cross using Fraction context.
## ENDDESCRIPTION
## KEYWORDS('fraction','Mendelian genetics','monohybrid')
## DBsubject('Genetics')
## DBchapter('Mendelian Genetics')
## DBsection('Monohybrid cross')
## Date('2026-02-15')
## Author('Textbook demo')
## Institution('LibreTexts')
DOCUMENT();
loadMacros(
"PGstandard.pl",
"MathObjects.pl",
"PGML.pl",
"contextFraction.pl",
"PGcourse.pl",
);
Context("Fraction");
$ans = Fraction(1, 4);
BEGIN_PGML
In a monohybrid cross between two heterozygous parents
(Aa x Aa), what fraction of the offspring are expected
to show the recessive phenotype (aa)?
Express your answer as a fraction.
Fraction with recessive phenotype = [________]{$ans}
END_PGML
ENDDOCUMENT();
contextPercent.pl
History: Long-running (MathObjects era).
## TITLE('Hardy-Weinberg allele frequency as percent')
## DESCRIPTION
## Calculate a heterozygote frequency using Hardy-Weinberg
## equilibrium and express it as a percentage.
## ENDDESCRIPTION
## KEYWORDS('percent','Hardy-Weinberg','allele frequency')
## DBsubject('Genetics')
## DBchapter('Population Genetics')
## DBsection('Hardy-Weinberg')
## Date('2026-02-15')
## Author('Textbook demo')
## Institution('LibreTexts')
DOCUMENT();
loadMacros(
"PGstandard.pl",
"MathObjects.pl",
"PGML.pl",
"contextPercent.pl",
"PGcourse.pl",
);
Context("Percent");
$q_sq = list_random(0.01, 0.04, 0.09, 0.16);
$q = sqrt($q_sq);
$p = 1 - $q;
$heterozygote_freq = 2 * $p * $q;
$ans = Percent($heterozygote_freq * 100);
BEGIN_PGML
In a population at Hardy-Weinberg equilibrium, the frequency of
the homozygous recessive genotype (aa) is [$q_sq].
What is the expected frequency of heterozygotes (Aa)?
Express your answer as a percentage.
Heterozygote frequency = [________]{$ans}
_(Include the % sign in your answer, e.g. "48%")_
END_PGML
ENDDOCUMENT();
contextArbitraryString.pl
History: Long-running (MathObjects era).
Complete examples appear in Fill in the Blank (fill in the blank) and Minimal Templates. See those pages for copy-paste ArbitraryString demos.
Grading and feedback
answerCustom.pl
History: Long-running (MathObjects era).
## TITLE('Accept pH values in a custom range')
## DESCRIPTION
## Use a custom answer checker to accept any pH value
## within the physiological range as correct.
## ENDDESCRIPTION
## KEYWORDS('custom checker','pH','physiology')
## DBsubject('Biochemistry')
## DBchapter('Buffers')
## DBsection('pH')
## Date('2026-02-15')
## Author('Textbook demo')
## Institution('LibreTexts')
DOCUMENT();
loadMacros(
"PGstandard.pl",
"MathObjects.pl",
"PGML.pl",
"answerCustom.pl",
"PGcourse.pl",
);
Context("Numeric");
$correct = Real(7.4);
$checker = custom_cmp(
$correct,
sub {
my ($correct_ans, $student_ans) = @_;
return ($student_ans >= 7.35 && $student_ans <= 7.45) ? 1 : 0;
},
);
BEGIN_PGML
Normal blood pH is tightly regulated within a narrow range.
Enter any pH value within the normal physiological range
for human blood.
pH = [________]{$checker}
_(Hint: normal blood pH is approximately 7.35 to 7.45)_
END_PGML
ENDDOCUMENT();
answerHints.pl
History: Long-running. Updated PG 2.16 (2021): no hints in preview mode.
## TITLE('Dilution calculation with targeted error feedback')
## DESCRIPTION
## Calculate a dilution with AnswerHints providing feedback
## for common student errors.
## ENDDESCRIPTION
## KEYWORDS('answer hints','dilution','feedback')
## DBsubject('Biochemistry')
## DBchapter('Solutions')
## DBsection('Dilutions')
## Date('2026-02-15')
## Author('Textbook demo')
## Institution('LibreTexts')
DOCUMENT();
loadMacros(
"PGstandard.pl",
"MathObjects.pl",
"PGML.pl",
"answerHints.pl",
"PGcourse.pl",
);
Context("Numeric");
$c1 = 10;
$v1 = 5;
$v2 = 50;
$c2 = Real($c1 * $v1 / $v2);
$wrong_multiply = Real($c1 * $v2 / $v1);
$wrong_ratio = Real($v2 / $v1);
BEGIN_PGML
You dilute [$v1] mL of a [$c1] mM solution to a final volume
of [$v2] mL. What is the final concentration in mM?
Final concentration = [________]{AnswerHints(
$wrong_multiply =>
"Check your formula: C2 = C1*V1/V2, not C1*V2/V1.",
$wrong_ratio =>
"You calculated the dilution factor, not the final concentration.",
$c2 => "Correct!",
)} mM
END_PGML
ENDDOCUMENT();
parserMultiAnswer.pl
History: Long-running (MathObjects era). Bug fix PG 2.16 for singleResult scoring.
## TITLE('Linked dilution blanks graded together')
## DESCRIPTION
## Enter two related dilution values that are graded
## together using MultiAnswer.
## ENDDESCRIPTION
## KEYWORDS('MultiAnswer','dilution','linked grading')
## DBsubject('Biochemistry')
## DBchapter('Solutions')
## DBsection('Dilutions')
## Date('2026-02-15')
## Author('Textbook demo')
## Institution('LibreTexts')
DOCUMENT();
loadMacros(
"PGstandard.pl",
"MathObjects.pl",
"PGML.pl",
"parserMultiAnswer.pl",
"PGcourse.pl",
);
Context("Numeric");
$vol = Real(5);
$conc = Real(2);
$ma = MultiAnswer($vol, $conc)->with(
singleResult => 0,
checker => sub {
my ($correct, $student) = @_;
my ($sv, $sc) = @{$student};
my @scores = (0, 0);
$scores[0] = 1 if ($sv * $sc == 10);
$scores[1] = 1 if ($sv * $sc == 10);
return [@scores];
},
);
BEGIN_PGML
You need a total of 10 mM*mL of solute. Choose a volume
and concentration whose product equals 10.
Volume (mL): [____]{$ma}
Concentration (mM): [____]{$ma}
END_PGML
ENDDOCUMENT();
PGgraders.pl
History: Long-running (WeBWorK 2.x+). custom_problem_grader_0_60_100 removed PG 2.20.
Note: The install_problem_grader call is setup code that works with PGML inline blanks. PGML blanks generate ANS() calls automatically, so no explicit ANS() is needed.
## TITLE('Calculate three enzyme rates with partial credit')
## DESCRIPTION
## Students calculate three enzyme reaction rates with
## partial credit using full_partial_grader from PGgraders.
## ENDDESCRIPTION
## KEYWORDS('partial credit','enzyme kinetics','grading')
## DBsubject('Biochemistry')
## DBchapter('Enzyme Kinetics')
## DBsection('Reaction rates')
## Date('2026-02-15')
## Author('Textbook demo')
## Institution('LibreTexts')
DOCUMENT();
loadMacros(
"PGstandard.pl",
"MathObjects.pl",
"PGML.pl",
"PGgraders.pl",
"PGcourse.pl",
);
install_problem_grader(~~&full_partial_grader);
Context("Numeric");
$r1 = Real(10 / 2);
$r2 = Real(20 / 4);
$r3 = Real(30 / 5);
BEGIN_PGML
Calculate the reaction rate (substrate/min) for each enzyme.
You will receive partial credit for each correct answer.
Enzyme A converts 10 units in 2 min. Rate = [________]{$r1}
Enzyme B converts 20 units in 4 min. Rate = [________]{$r2}
Enzyme C converts 30 units in 5 min. Rate = [________]{$r3}
END_PGML
ENDDOCUMENT();
weightedGrader.pl
History: Long-running (WeBWorK 2.x+).
Note: Use weight_ans() to wrap a checker with a weight. The wrapped checker works with PGML inline blanks.
## TITLE('Weighted dilution: volume then concentration')
## DESCRIPTION
## A two-part dilution problem where the concentration
## calculation is worth more, using weightedGrader.
## ENDDESCRIPTION
## KEYWORDS('weighted grading','dilution','partial credit')
## DBsubject('Biochemistry')
## DBchapter('Solutions')
## DBsection('Dilutions')
## Date('2026-02-15')
## Author('Textbook demo')
## Institution('LibreTexts')
DOCUMENT();
loadMacros(
"PGstandard.pl",
"MathObjects.pl",
"PGML.pl",
"weightedGrader.pl",
"PGcourse.pl",
);
install_weighted_grader();
Context("Numeric");
$dilution_factor = Real(10);
$final_conc = Real(5);
BEGIN_PGML
You dilute a 50 mM stock solution by a factor of 10.
Part 1 (30%): What is the dilution factor?
[________]{weight_ans($dilution_factor->cmp, 30)}
Part 2 (70%): What is the final concentration in mM?
[________]{weight_ans($final_conc->cmp, 70)}
END_PGML
ENDDOCUMENT();
Layout and scaffolding
niceTables.pl
History: Long-running. Overhauled PG 2.18 (2023). Auto-loaded in PGML PG 2.19.
Complete examples appear in Making Tables with niceTables (making tables with niceTables). See that page for copy-paste DataTable and LayoutTable demos.
scaffold.pl
History: Long-running. Replaces deprecated compoundProblem*.pl (deprecated PG 2.19).
Authoring warning: Each Section block requires its own BEGIN_PGML/END_PGML pair. Variables defined in one section are visible in later sections, but each section's PGML block must be self-contained. Rendering quirks may appear with deeply nested scaffolds.
## TITLE('Two-section genetics: genotype then phenotype ratio')
## DESCRIPTION
## A scaffolded problem where students first identify a
## genotype, then calculate the phenotype ratio in a
## separate section.
## ENDDESCRIPTION
## KEYWORDS('scaffold','genetics','multi-section')
## DBsubject('Genetics')
## DBchapter('Mendelian Genetics')
## DBsection('Monohybrid cross')
## Date('2026-02-15')
## Author('Textbook demo')
## Institution('LibreTexts')
DOCUMENT();
loadMacros(
"PGstandard.pl",
"MathObjects.pl",
"PGML.pl",
"scaffold.pl",
"parserRadioButtons.pl",
"PGcourse.pl",
);
Context("Numeric");
$genotype_radio = RadioButtons(
[ "AA x AA", "Aa x Aa", "AA x aa", "aa x aa" ],
"Aa x Aa",
);
$ratio = Real(3/4);
Scaffold::Begin();
Section::Begin("Part 1: Identify the cross");
BEGIN_PGML
Both parents are heterozygous for a single gene with
complete dominance. Which cross represents this mating?
[_]{$genotype_radio}
END_PGML
Section::End();
Section::Begin("Part 2: Calculate the ratio");
BEGIN_PGML
For the cross Aa x Aa, what fraction of offspring will
show the dominant phenotype?
Express your answer as a decimal.
Fraction dominant = [________]{$ratio}
END_PGML
Section::End();
Scaffold::End();
ENDDOCUMENT();
PGgraphmacros.pl
History: Long-running core macro (WeBWorK 2.x+).
Complete examples appear in Making Graphs (making graphs). See that page for copy-paste graph demos with biology-themed content.


