autoharp is an R package for semi-automatic
grading of R and R Markdown (Rmd/qmd) scripts. It was designed
at the National University of Singapore to handle the practical
challenges of grading programming assignments at scale:
for loop?”autoharp achieves this through four complementary
layers:
| Layer | What it Checks |
|---|---|
| Output correctness | Objects match the reference solution (typed, tolerance-aware) |
| Static code analysis | AST structure: e.g., no for loops, correct function
signature |
| Runtime profiling | Execution time and peak memory usage per submission |
| Code style (lint) | lintr-based style violations count |
The four-phase autoharp grading workflow: Prepare → Distribute → Grade → Review
The typical autoharp workflow has four phases:
autoharp.objs/autoharp.scalars chunk
options)populate_soln_env() then
render_one() per student; each runs in a sandboxed
R processYou can install autoharp from CRAN with:
You can also install the development version from GitHub:
Then load the package:
The heart of autoharp is the solution
template - an R Markdown file that does two things
simultaneously:
Two special chunk options are used in test chunks (chunks whose names match a pattern, default “test”):
| Chunk Option | Purpose |
|---|---|
autoharp.objs |
Lists object names to copy from the solution environment so tests can compare student objects against solution objects |
autoharp.scalars |
Lists scalar variable names that will be created by the test code; these become columns in the output data frame |
Why Rmd? Grading complete documents (rather than isolated snippets) ensures students practice good scientific computing habits: their entire analysis must render cleanly.
Solution Template (.Rmd)
│
▼
populate_soln_env() --> Solution Environment + Test Script
│
▼
render_one(student.Rmd) --> Grading Results Data Frame
│
▼
log_summary() --> Summary Report
Each render_one() call: 1. Launches a fresh,
sandboxed R process (via
parallel::makePSOCKcluster) 2. Checks for forbidden calls
(system(), setwd(), etc.) 3. Knits the
student’s Rmd with autoharp hooks active 4. Runs the test
script in the student’s environment 5. Returns a data frame with status,
runtime, memory, and test results
Suppose you assign students the following problem:
Write a function
rf(n)that generatesnrandom variates from the density \(f(x) = 4x^3\), \(0 < x < 1\). Use the inverse transform method. Then create a vectorXof 10,000 variates usingrf().
Create solution_template.Rmd:
---
title: "Solution Template"
output: html_document
---
```{r solution}
rf <- function(n) {
u <- runif(n)
u^(1/4) # inverse CDF of f(x) = 4x^3
}
X <- rf(10000)
```
```{r test01, autoharp.objs=c("rf", "X"), autoharp.scalars=c("lenX", "lenfn", "meanX", "sdX")}
# Compare student objects with solution objects
lenX <- (length(X) == length(.X))
lenfn <- (length(formals(rf)) == length(formals(.rf)))
# Check statistical properties
meanX <- (abs(mean(X) - 0.8) < 0.02)
sdX <- (abs(sd(X) - 0.163) < 0.02)
```Suppose a student submits student01.Rmd:
result <- render_one(
rmd_name = "student01.Rmd",
out_dir = "output/",
knit_root_dir = getwd(),
soln_stuff = soln
)
# The result is a one-row data frame
print(result)The output data frame contains:
| Column | Description |
|---|---|
fname |
Student’s filename |
time_stamp |
Timestamp of the grading run |
run_status |
"SUCCESS", "FAIL", or
"UNKNOWN" |
run_time |
Total execution time (seconds) |
run_mem |
Memory usage of the environment |
lenX, lenfn, … |
Scalar test results defined in autoharp.scalars |
# Grade all students in a directory
student_files <- list.files("submissions/", pattern = "\\.Rmd$", full.names = TRUE)
results_list <- lapply(student_files, function(f) {
render_one(rmd_name = f, out_dir = "output/", knit_root_dir = getwd(),
soln_stuff = soln)
})
all_results <- do.call(rbind, results_list)
# Print a summary from the log file
log_summary("output/render_one.log")autoharp integrates with the lintr package
to count style violations:
# Count lint violations in a single script
lint_count <- count_lints_one("student01.R")
# Count across all submissions
all_lints <- count_lints_all(
file_names = list.files("submissions/", pattern = "\\.R$", full.names = TRUE)
)
print(all_lints)You can run lint checks separately from the main grading pipeline using these functions.
For R Markdown submissions, verify that the file is a valid Rmd:
# Check that the submitted file is a valid Rmd
# Returns TRUE if file has .Rmd extension, YAML header, and R chunks
rmd_check <- check_rmd(fname = "student01.Rmd")
print(rmd_check)For large classes, the Grading App provides a browser-based interface that wraps the entire workflow:
The Grading App has five tabs:
render_one() for all
submissions with progress tracking.See the Shiny Apps Guide for full details.
for loops, check function signatures, and more