Skip to content

Commit

Permalink
Added documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
kagalenko-m-b committed Apr 25, 2024
1 parent 3025971 commit 1361bcf
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 12 deletions.
44 changes: 44 additions & 0 deletions Generated_methods.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
## Methods of the generated functions

Each `@problem` macro generates in the workspace five methods to be used for
producing the individual problems. Let's take an example:
```julia
@problem sub_add begin
z ~ rand(7:9)
w ~ rand(1:5)
@solution begin
zw_sub ~ z - w
zw_add ~ z + w
end
@text raw""" Find the difference \(c = a - b\) and sum \(d = a + b\)
of two values: \(a = %z%\) and \(b = %w%\)
"""
@text_solution raw"""
Difference is equal to \(c = %zw_sub%\), sum is equal to \(c = %zw_add%\)
"""
end
```

That macro call is equivalent to manually defining the following five methods:

```julia
function sub_add()
z = rand(7:9)
w = rand(1:5)
zw_sub,zw_add = sub_add(z, w)
return z,w,zw_sub,zw_add
end

function sub_add(z, w)
zw_sub = z - w
zw_add = z + w
return zw_sub,zw_add
end

sub_add(::Val{:vars}) = [:z, :w, :zw_sub, :zw_add]

sub_add(::Val{:text}) = " Find the difference \\(c = a - b\\) and sum \\(d = a + b\\)\nof two values: \\(a = %z%\\) and \\(b = %w%\\)\n"

sub_add(::Val{:solution_text}) = " Difference is equal to \\(c = %zw_sub%\\), sum is equal to \\(c = %zw_add%\\)\n"

```
71 changes: 70 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,72 @@
# MakeProblemSet

[![Build Status](https://github.com/kagalenko-m-b/MakeProblemSet.jl/actions/workflows/CI.yml/badge.svg?branch=master)](https://github.com/kagalenko-m-b/MakeProblemSet.jl/actions/workflows/CI.yml?query=branch%3Amaster)
The goal of this project is to facilitate the creation of problem assignments
for a group of students. A problem set consists of several problem templates with
text and placeholder variables. Percentage signs before and after mark the
placeholder variables within the template text. Function `problemset_latex()`
then generates the latex source of the assignment and solutions for a vector of students'
names. In the latex source assigned values replace the placeholder variables.
Every placeholder variable must appear at least once as the left-hand of
an assignment where the tilde replaces the equality sign.

This is an example set:
```julia
@problemset my_set begin
@problem pool begin
pool_size_liters ~ rand(1000:10:2000)
inflow_liters_sec ~ rand(10:20)
outflow_max = inflow_liters_sec ÷ 2
outflow_liters_sec ~ rand(1:outflow_max)
@solution begin
fill_rate = inflow_liters_sec - outflow_liters_sec
time_to_fill = pool_size_liters / fill_rate
time_to_fill_min ~ round(time_to_fill / 60, digits=3)
leaked_liters ~ round(time_to_fill*outflow_liters_sec, digits=3)
end
@text """
An empty pool can hold %pool_size_liters% liters of water. Pipe
fills it at the rate %inflow_liters_sec%~liters/sec while another
drains it at the rate %outflow_liters_sec%~liters/sec. How many minutes
will it take to fill the pool and how many liters of water will
drain out by the time the pool is full?
"""
@text_solution """
It will take %time_to_fill_min% minutes to fill the pool and
%leaked_liters%~liters of water will drain out.
"""
end
@problem addition begin
x ~ rand(1:3)
y ~ rand(2:5)
@solution xy ~ x + y
@text raw""" Find the sum \(c = a + b\) of two values: \(a = %x%\) and
\(b = %y%\)
"""
@text_solution raw"""
Sum is equal to \(c = %xy%\)
"""
end
@problem subtraction begin
z ~ rand(7:9)
w ~ rand(1:5)
@solution zw ~ z - w
@text raw""" Find the difference \(c = a - b\) of two values: \(a = %z%\) and
\(b = %w%\)
"""
@text_solution raw"""
Difference is equal to \(c = %zw%\)
"""
end
end
```
After exection of this macro, there's a vector in workspace named `my_set`
that contains three functions `my_set_pool()`, `my_set_addition()` and
`my_set_subtraction()`. Text-generating function makes use of their
[methods](Generated_methods.md). This is how an assignment may be produced:
```julia
student_names = ["A", "B", "C"];
rng_seed = 123;
txt,txt_sol = problemset_latex(student_names, my_set, 2=>1:3, rng_seed);
write("problems.tex", latex_preamble*txt);
write("solutions.tex", latex_preamble*txt);
```
64 changes: 55 additions & 9 deletions src/MakeProblemSet.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,24 @@ module MakeProblemSet
using MacroTools
using Random

export @problem, @problemset, problemset_latex
export @problem, @problemset, problemset_latex, latex_preamble,latex_preamble_ru

include("problem_compiler.jl")

latex_preamble = """
\\documentclass[a4paper,12pt,notitlepage]{article}
\\usepackage{amsmath}
\\usepackage[left=1.cm,right=1cm,top=1cm,bottom=1cm]{geometry}
\\pagenumbering{gobble}
\\usepackage{fontspec}
\\usepackage{polyglossia}
\\usepackage{csvmerge}
\\usepackage{float}
\\usepackage{graphicx}
\\usepackage{bookmark}
\\setmainfont{Liberation Serif}
\\setsansfont{Liberation Sans}\n\n"""

function select_problems(subsets::Vector{<:Pair{<:Integer,<:AbstractRange{<:Integer}}})
idx = Int[]
for s in subsets
Expand All @@ -18,32 +32,64 @@ function select_problems(subsets::Vector{<:Pair{<:Integer,<:AbstractRange{<:Inte

return idx
end
select_problems(subset::Pair{Int, UnitRange{Int}}) = select_problems([subset])

function select_problems(subset::Pair{<:Integer,<:AbstractRange{<:Integer}})
return select_problems([subset])
end

"""
problemset_latex(args...) -> (String,String)
Generate the latex source of the problems and solutions.
# Arguments
- `names::AbstractVector{String}`: Students' names
- `problems::AbstractVector{Function}`: Vector of functions defined using @problem macro
- `subsets::Union{Pair,Vector{<:Pair}}`: Subset specification or vector of specifications
- `rng_seed::Integer`: Random number generator's seed to make generated sets repeatable
Subset specifications instructs the function how to pick problems to be assigned to a
student from the supplied vector. For example, the specification [1=>1:3, 2=>4:7]
will select one out of the first three problems, two out of the problems four to seven
and then combine the results. If the ranges overlap, only unique problem numbers are kept.
# Example
```julia-repl
julia> student_names = ["A", "B", "C"];
julia> rng_seed = 123
julia> txt,txt_sol = problemset_latex(student_names, my_set, 2=>1:3, rng_seed);
julia> write("problems.tex", latex_preamble*txt);
julia> write("solutions.tex", latex_preamble*txt);
```
"""
function problemset_latex(
names::AbstractVector{String},
problems::AbstractVector{Function},
subsets,
subsets::Union{Pair,Vector{<:Pair}},
rng_seed::Integer
)
N = length(names)
txt = ""
txt = "\\begin{document}\n"
txt_sol = txt
for n in 1:N
txt *= "\\section{$(names[n])}\n"
txt_sol *= "\\section{$(names[n])}\n"
problems_active = select_problems(subsets)
for p in problems_active
Random.seed!(rng_seed + n + p)
pr = problems[p]
condition = build_text(:text, pr)
solution = build_text(:solution_text, pr)
txt *= "\\underline{Задача $(p):}\n\n"
txt *= "\\ifdefined\\issolution\n$(solution)\n\\else\n"
txt *= "$(condition)\n\\fi}\n\n"
txt *= "\\underline{Задача $(p):}\n\n$(condition)\n\n"
txt_sol *= "\\underline{Задача $(p):}\n\n$(solution)\n\n"
end
txt *= "\\newpage\n"
txt_sol *= "\\newpage\n"
end

return txt
txt *= "\\end{document}\n"
txt_sol *= "\\end{document}\n"
return txt,txt_sol
end

function build_text(kind::Symbol, pr::Function)
Expand Down
4 changes: 2 additions & 2 deletions src/problem_compiler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ function process_body(mod, name, body)
sol_vars = Symbol[]
sol_body = process_body!(mod, sol_vars, sol_body)
problemdef[:sol_body] = sol_body
problemdef[:sol_vars] = sol_vars
problemdef[:sol_vars] = unique(sol_vars)
#
body_args = map(x->is_macro(x, :solution) ? :(:solution) : x, body_args)
cond_body = Expr(body.head, body_args...)
cond_vars = Symbol[]
cond_body = process_body!(mod, cond_vars, cond_body)
problemdef[:cond_vars] = cond_vars
problemdef[:cond_vars] = unique(cond_vars)
problemdef[:cond_body] = cond_body

return problemdef
Expand Down

0 comments on commit 1361bcf

Please sign in to comment.