Compare commits
14 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7275cdd5d9 | |||
| eaa2e72dfa | |||
| 8668ad46e8 | |||
| 83a32d4fa0 | |||
| 482bc0a088 | |||
| 24a29b2d89 | |||
| 880b534b35 | |||
| 07d8825d14 | |||
| 0ba530f4e3 | |||
| 1469d538df | |||
|
|
6114e7b036 | ||
|
|
c81d8f65b2 | ||
|
|
921082f61d | ||
| 4c496fc7a9 |
7 changed files with 323 additions and 42 deletions
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Gaspard Jankowiak
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
154
README.md
Normal file
154
README.md
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
# The `exam` package
|
||||||
|
|
||||||
|
This is a simple package to typeset exams. With it, you can define exercises,
|
||||||
|
and items, each with a number of points.
|
||||||
|
A summary is then printed on the front page.
|
||||||
|
|
||||||
|
## Installation (Linux only)
|
||||||
|
|
||||||
|
#### Using [`typship`](https://github.com/sjfhsjfh/typship)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
typship download -n local https://imsc.uni-graz.at/git/gjankowiak/typst-exam/
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Manually
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.local/share/typst/packages/local/exam
|
||||||
|
cd ~/.local/share/typst/packages/local/exam
|
||||||
|
curl https://imsc.uni-graz.at/git/gjankowiak/typst-exam/archive/v0.3.0.tar.gz | tar zx --xform 's/typst-exam/0.3.0/'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quickstart (CLI)
|
||||||
|
|
||||||
|
This will create an `exam` directory in the current directory,
|
||||||
|
containing the example below.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
typst init @local/exam exam
|
||||||
|
```
|
||||||
|
|
||||||
|
Tested typst version: `0.14.0`.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Writing an exam is now easy (see [API](#api) for all options):
|
||||||
|
|
||||||
|
```typ
|
||||||
|
#import "@local/exam:0.3.0": exam, exercise, exercise-items, mtext, horiz-only
|
||||||
|
|
||||||
|
#show: exam.with(
|
||||||
|
title: "Exam",
|
||||||
|
|
||||||
|
course-title: [Abstract Binary Computation & Elegant Finite Graphs],
|
||||||
|
|
||||||
|
institution: [Super University],
|
||||||
|
|
||||||
|
date: "1. January 1970",
|
||||||
|
course-short-title: "ABC & EFG",
|
||||||
|
|
||||||
|
course-code: "π",
|
||||||
|
duration-minutes: "90",
|
||||||
|
|
||||||
|
ask-trainer-name: false,
|
||||||
|
ask-group: true,
|
||||||
|
|
||||||
|
language: "en",
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Defining exercises and items
|
||||||
|
|
||||||
|
A new exercise can be started using `#exercise("title", nb_points)`, for example:
|
||||||
|
|
||||||
|
```typ
|
||||||
|
#exercise("Relations and their properties", 2)
|
||||||
|
|
||||||
|
Consider the relation $R subset NN^2$, defined as follows:
|
||||||
|
|
||||||
|
$ (x, y) in R #h(0.5cm) <==> #h(0.5cm) x + y #mtext[is odd]. $
|
||||||
|
|
||||||
|
Is $R$ reflexive? transitive? symmetric? antisymmetric?
|
||||||
|
```
|
||||||
|
|
||||||
|
On can also defined (sub-)items for the exercise using `#exercise-items(override_points:true, numbering:"a)", items)`,
|
||||||
|
where `items` is an array of `(nb_points, statement)`. By default, the number of points the exercise
|
||||||
|
is worth is recomputed as the sum of points for all items. This behaviour can be turned off by
|
||||||
|
setting `override_points: false`.
|
||||||
|
|
||||||
|
```typ
|
||||||
|
#exercise("Properties of functions", 3)
|
||||||
|
|
||||||
|
Let $f : (0, +infinity) → (0, +infinity)$ with $f (x) = e^(-x)$.
|
||||||
|
|
||||||
|
#exercise-items((
|
||||||
|
(1, [Is $f$ injective?]),
|
||||||
|
(1, [Is $f$ surjective?]),
|
||||||
|
(1, [Is $f$ bijective?]),
|
||||||
|
))
|
||||||
|
|
||||||
|
#exercise("Logical operators", 3.14)
|
||||||
|
|
||||||
|
Consider the following truthtable:
|
||||||
|
|
||||||
|
#align(center, table(
|
||||||
|
stroke: horiz-only(0.5pt),
|
||||||
|
columns: (auto, auto, auto),
|
||||||
|
align: center,
|
||||||
|
[$A$], [$B$], [$A or B$],
|
||||||
|
[1], [1], [1],
|
||||||
|
[1], [0], [1],
|
||||||
|
[0], [1], [1],
|
||||||
|
[0], [0], [1]
|
||||||
|
))
|
||||||
|
|
||||||
|
#exercise-items(override-points: false, numbering: "i)", (
|
||||||
|
(1, [Is the truthtable correct?]),
|
||||||
|
(1, [If not, fix it.]),
|
||||||
|
))
|
||||||
|
```
|
||||||
|
|
||||||
|
All together, this should output something like:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
You can also [play with it](https://typst.app/project/rrQbGYoQ3pePdfSMl0tmv4) on the Typst Playground (you might need an account).
|
||||||
|
|
||||||
|
### Utilities
|
||||||
|
|
||||||
|
These are used in the example above.
|
||||||
|
|
||||||
|
- `mtext(str)` to typeset text within math mode using the default text font.
|
||||||
|
- `frame(stroke_width)` provides `stroke` for use in a `table`, horizontal lines only,
|
||||||
|
top and bottom lines are bold.
|
||||||
|
|
||||||
|
### API
|
||||||
|
|
||||||
|
```typ
|
||||||
|
#exam(
|
||||||
|
title: "%KLAUSUR or EXAM%",
|
||||||
|
|
||||||
|
course-title: [%COURSE_TITLE_FIRST_LINE% \ %COURSE_TITLE_SECOND_LINE%],
|
||||||
|
course-short-title: "%course-short-title%",
|
||||||
|
course-code: "%course-code%",
|
||||||
|
|
||||||
|
date: "%DATE%",
|
||||||
|
|
||||||
|
institution: smallcaps("NAWI Graz"),
|
||||||
|
|
||||||
|
duration-minutes: "%DURATION%",
|
||||||
|
|
||||||
|
ask-student-number: false,
|
||||||
|
ask-trainer-name: false,
|
||||||
|
ask-group: true,
|
||||||
|
|
||||||
|
instructions: none,
|
||||||
|
|
||||||
|
language: "de", // or "en"
|
||||||
|
font-size: 12pt,
|
||||||
|
paper-size: "a4",
|
||||||
|
|
||||||
|
exercise-numbering: "1",
|
||||||
|
)
|
||||||
|
```
|
||||||
BIN
screenshot-example.png
Normal file
BIN
screenshot-example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
|
|
@ -2,8 +2,6 @@
|
||||||
* Customisation
|
* Customisation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#let lightgray = rgb("#eeeeee")
|
|
||||||
|
|
||||||
// Default font (embedded in Typst)
|
// Default font (embedded in Typst)
|
||||||
#let font-text = "libertinus serif"
|
#let font-text = "libertinus serif"
|
||||||
|
|
||||||
|
|
@ -13,9 +11,9 @@
|
||||||
* Macros
|
* Macros
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Frame for table, no vertical lines, top and bottom are bold
|
// stroke helper function for table, no vertical lines, top and bottom are bold
|
||||||
// s: stroke, to specify e.g. the width and color of strokes
|
// s: stroke, to specify e.g. the width and color of strokes
|
||||||
#let frame(s) = (x, y) => (
|
#let horiz-only(s) = (x, y) => (
|
||||||
left: none,
|
left: none,
|
||||||
right: none,
|
right: none,
|
||||||
top: if y > 0 { s } else { 2*s },
|
top: if y > 0 { s } else { 2*s },
|
||||||
|
|
@ -57,13 +55,13 @@
|
||||||
// - int is the number of points this item is worth,
|
// - int is the number of points this item is worth,
|
||||||
// - expression is the item to typeset.
|
// - expression is the item to typeset.
|
||||||
// example:
|
// example:
|
||||||
// #exercise_items((
|
// #exercise-items((
|
||||||
// (1, [Untersuchen Sie, ob $f$ injektiv ist.]),
|
// (1, [Untersuchen Sie, ob $f$ injektiv ist.]),
|
||||||
// (1, [Untersuchen Sie, ob $f$ surjektiv ist.]),
|
// (1, [Untersuchen Sie, ob $f$ surjektiv ist.]),
|
||||||
// (1, [Geben Sie im Falle ihrer Existenz die Umkehrfunktion $f^(−1)$ an.])
|
// (1, [Geben Sie im Falle ihrer Existenz die Umkehrfunktion $f^(−1)$ an.])
|
||||||
//))
|
//))
|
||||||
#let exercise_items(items) = block[
|
#let exercise-items(override-points: true, numbering: "a)", items) = block[
|
||||||
#let c = counter("exercise_items")
|
#let c = counter("exercise-items")
|
||||||
#let exercise_points = 0
|
#let exercise_points = 0
|
||||||
#c.update(0)
|
#c.update(0)
|
||||||
#table(
|
#table(
|
||||||
|
|
@ -78,21 +76,24 @@
|
||||||
pt = get_L("points_short")
|
pt = get_L("points_short")
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
[#c.step()#v(4pt) *#context c.display("a)")* #statement],
|
[#c.step()#v(4pt) *#context c.display(numbering)* #statement],
|
||||||
rect(radius:1cm, inset: (x:5pt, y:3pt), stroke: gray+0.5pt)[#points #pt],
|
rect(radius:1cm, inset: (x:5pt, y:3pt), stroke: gray+0.5pt)[#points #pt],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
#context {
|
|
||||||
state("exercise-registry").update( arr => {
|
#if override-points {
|
||||||
if (arr.len() == 0) {
|
context {
|
||||||
arr
|
state("exercise-registry").update( arr => {
|
||||||
} else {
|
if (arr.len() == 0) {
|
||||||
let new_arr = arr.slice(0, arr.len()-1)
|
arr
|
||||||
new_arr.push((arr.last().at(0), arr.last().at(1), exercise_points))
|
} else {
|
||||||
new_arr
|
let new_arr = arr.slice(0, arr.len()-1)
|
||||||
}
|
new_arr.push((arr.last().at(0), arr.last().at(1), exercise_points))
|
||||||
})
|
new_arr
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -100,43 +101,45 @@
|
||||||
// Used to compute the overview table (see below)
|
// Used to compute the overview table (see below)
|
||||||
// title: string, title of the exercise
|
// title: string, title of the exercise
|
||||||
// points: int, the number of points the exercise is worth
|
// points: int, the number of points the exercise is worth
|
||||||
#let exercise_header(title, points) = {
|
#let exercise(title, points) = {
|
||||||
// state("exercise_points").update(points)
|
// state("exercise_points").update(points)
|
||||||
let exercise_counter = counter("exercise")
|
let exercise_counter = counter("exercise")
|
||||||
exercise_counter.step()
|
exercise_counter.step()
|
||||||
context {
|
context {
|
||||||
let current_exercise = exercise_counter.get().at(0)
|
let current_exercise = exercise_counter.get().at(0)
|
||||||
|
let exercise-numbering = state("exercise-numbering").get()
|
||||||
let exercise_registry = state("exercise-registry")
|
let exercise_registry = state("exercise-registry")
|
||||||
let final_points = 0
|
let final_points = 0
|
||||||
if (exercise_registry.final().len() >= current_exercise) {
|
if (exercise_registry.final().len() >= current_exercise) {
|
||||||
final_points = exercise_registry.final().at(exercise_counter.get().at(0)-1).at(2)
|
final_points = exercise_registry.final().at(exercise_counter.get().at(0)-1).at(2)
|
||||||
}
|
}
|
||||||
let exercise_label = exercise_counter.display()
|
let exercise_label = exercise_counter.display(exercise-numbering)
|
||||||
exercise_registry.update(entries => entries + ((exercise_label, title, points),))
|
exercise_registry.update(entries => entries + ((exercise_label, title, points),))
|
||||||
block(radius: 1cm, stroke: none, fill: lightgray, inset: 1pt,
|
block(radius: 1cm, stroke: none, fill: luma(state("fill-luma").get()), inset: 1pt,
|
||||||
table(
|
table(
|
||||||
columns: (auto, 1fr, auto),
|
columns: (auto, 1fr, auto),
|
||||||
align: (left, center, right),
|
align: (left, center, right),
|
||||||
stroke: none,
|
stroke: none,
|
||||||
[#rect(radius:1cm, outset: 4pt, inset: (x:2pt, y:0pt), fill: white)[*#get_L("exercise") #context{exercise_counter.display()}*]], [#title], [#rect(radius:1cm, outset: 4pt, inset: (x:2pt, y:0pt), fill: white)[*#format_points(final_points)*]],
|
[#rect(radius:1cm, outset: 4pt, inset: (x:2pt, y:0pt), fill: white)[*#get_L("exercise") #context{exercise_counter.display(exercise-numbering)}*]], [#title], [#rect(radius:1cm, outset: 4pt, inset: (x:2pt, y:0pt), fill: white)[*#format_points(final_points)*]],
|
||||||
)
|
)
|
||||||
)}}
|
)}}
|
||||||
|
|
||||||
#let exam(
|
#let exam(
|
||||||
title: "%KLAUSUR or EXAM%",
|
title: "%KLAUSUR or EXAM%",
|
||||||
|
|
||||||
course_title: [%COURSE_TITLE_FIRST_LINE% \ %COURSE_TITLE_SECOND_LINE%],
|
course-title: [%COURSE_TITLE_FIRST_LINE% \ %COURSE_TITLE_SECOND_LINE%],
|
||||||
course_short_title: "%COURSE_SHORT_TITLE%",
|
course-short-title: "%course-short-title%",
|
||||||
|
course-code: "%course-code%",
|
||||||
|
|
||||||
date: "%DATE%",
|
date: "%DATE%",
|
||||||
|
|
||||||
institution: smallcaps("NAWI Graz"),
|
institution: smallcaps("NAWI Graz"),
|
||||||
|
|
||||||
course_number: "%COURSE_NUMBER%",
|
duration-minutes: "%DURATION%",
|
||||||
duration_minutes: "%DURATION%",
|
|
||||||
|
|
||||||
ask_student_number: false,
|
ask-student-number: false,
|
||||||
ask_trainer_name: false,
|
ask-trainer-name: false,
|
||||||
ask_group: true,
|
ask-group: true,
|
||||||
|
|
||||||
instructions: none,
|
instructions: none,
|
||||||
|
|
||||||
|
|
@ -144,6 +147,10 @@
|
||||||
font-size: 12pt,
|
font-size: 12pt,
|
||||||
paper-size: "a4",
|
paper-size: "a4",
|
||||||
|
|
||||||
|
exercise-numbering: "1",
|
||||||
|
|
||||||
|
fill-luma: 200,
|
||||||
|
|
||||||
body
|
body
|
||||||
) = {
|
) = {
|
||||||
|
|
||||||
|
|
@ -155,6 +162,9 @@
|
||||||
set page(paper: paper-size)
|
set page(paper: paper-size)
|
||||||
set text(size: font-size, lang: language, hyphenate: true)
|
set text(size: font-size, lang: language, hyphenate: true)
|
||||||
|
|
||||||
|
state("exercise-numbering").update(exercise-numbering)
|
||||||
|
state("fill-luma").update(fill-luma)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Localization
|
* Localization
|
||||||
*/
|
*/
|
||||||
|
|
@ -162,7 +172,7 @@ set text(size: font-size, lang: language, hyphenate: true)
|
||||||
let translations = none
|
let translations = none
|
||||||
if language == "en" {
|
if language == "en" {
|
||||||
translations = (
|
translations = (
|
||||||
course_number: "Course number",
|
course-code: "Course code",
|
||||||
exam_duration: "Exam duration",
|
exam_duration: "Exam duration",
|
||||||
student_name: "Name",
|
student_name: "Name",
|
||||||
student_number: "Student number",
|
student_number: "Student number",
|
||||||
|
|
@ -180,7 +190,7 @@ if language == "en" {
|
||||||
)
|
)
|
||||||
} else if language == "de" {
|
} else if language == "de" {
|
||||||
translations = (
|
translations = (
|
||||||
course_number: "LV-Nummer",
|
course-code: "LV-Nummer",
|
||||||
exam_duration: "Arbeitszeit",
|
exam_duration: "Arbeitszeit",
|
||||||
student_name: "Name",
|
student_name: "Name",
|
||||||
student_number: "Matrikelnr.",
|
student_number: "Matrikelnr.",
|
||||||
|
|
@ -215,7 +225,7 @@ let exercise_overview_table() = context {
|
||||||
columns.push(1fr)
|
columns.push(1fr)
|
||||||
[== #get_L("points_obtained")]
|
[== #get_L("points_obtained")]
|
||||||
set text(size: 14pt)
|
set text(size: 14pt)
|
||||||
block(radius: 0.3cm, stroke: none, fill: lightgray, inset: (x:0pt, y:4pt),
|
block(radius: 0.3cm, stroke: none, fill: luma(state("fill-luma").get()), inset: (x:0pt, y:4pt),
|
||||||
table(
|
table(
|
||||||
columns: columns,
|
columns: columns,
|
||||||
stroke: none,
|
stroke: none,
|
||||||
|
|
@ -260,7 +270,7 @@ set page(
|
||||||
inset: 0pt,
|
inset: 0pt,
|
||||||
align: (x, y) => { if x < 2 { left+bottom } else { right }},
|
align: (x, y) => { if x < 2 { left+bottom } else { right }},
|
||||||
columns: (auto, 1fr, auto),
|
columns: (auto, 1fr, auto),
|
||||||
[#institution], [], [#course_short_title \ _#(date)_],
|
[#institution], [], [#course-short-title \ _#(date)_],
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
footer: [#align(right)[#context(counter(page).display("1 / 1", both:true))]],
|
footer: [#align(right)[#context(counter(page).display("1 / 1", both:true))]],
|
||||||
|
|
@ -271,31 +281,33 @@ set page(
|
||||||
v(1fr)
|
v(1fr)
|
||||||
align(center)[= #title]
|
align(center)[= #title]
|
||||||
v(0.5cm)
|
v(0.5cm)
|
||||||
align(center)[== #course_title]
|
align(center)[== #course-title]
|
||||||
v(1fr)
|
v(1fr)
|
||||||
|
|
||||||
[*#get_L("course_number")*: #course_number* \ #get_L("exam_duration"): #duration_minutes #get_L("minutes")*]
|
[*#get_L("course-code")*: #course-code \ *#get_L("exam_duration"): #duration-minutes #get_L("minutes")*]
|
||||||
|
|
||||||
v(0.5cm)
|
v(0.5cm)
|
||||||
|
|
||||||
block(radius: 0.2cm, fill: lightgray, inset: 3pt, clip: true, table(
|
context{
|
||||||
fill: lightgray,
|
block(radius: 0.2cm, fill: luma(state("fill-luma").get()), inset: 3pt, clip: true, table(
|
||||||
|
fill: luma(state("fill-luma").get()),
|
||||||
stroke: none,
|
stroke: none,
|
||||||
columns: (auto, 1fr),
|
columns: (auto, 1fr),
|
||||||
gutter: 3pt,
|
gutter: 3pt,
|
||||||
inset: (left:7pt, right:0pt, y:0pt),
|
inset: (left:7pt, right:0pt, y:0pt),
|
||||||
align: horizon,
|
align: horizon,
|
||||||
[#get_L("student_name")], rect(fill: white, width: 100%, radius: 0.2cm),
|
[#get_L("student_name")], rect(fill: white, width: 100%, radius: 0.2cm),
|
||||||
.. if ask_student_number {
|
.. if ask-student-number {
|
||||||
([#get_L("student_number")], rect(fill: white, width: 100%, radius: 0.2cm))
|
([#get_L("student_number")], rect(fill: white, width: 100%, radius: 0.2cm))
|
||||||
},
|
},
|
||||||
..if ask_trainer_name {
|
..if ask-trainer-name {
|
||||||
([#get_L("trainer_name")], rect(fill: white, width: 100%, radius: 0.2cm))
|
([#get_L("trainer_name")], rect(fill: white, width: 100%, radius: 0.2cm))
|
||||||
},
|
},
|
||||||
..if ask_group {
|
..if ask-group {
|
||||||
([#get_L("group")], rect(fill: white, width: 100%, radius: 0.2cm))
|
([#get_L("group")], rect(fill: white, width: 100%, radius: 0.2cm))
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
}
|
||||||
|
|
||||||
v(0.5cm)
|
v(0.5cm)
|
||||||
|
|
||||||
88
template/exam.typ
Normal file
88
template/exam.typ
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
#import "@local/exam:0.3.0": exam, exercise, exercise-items, horiz-only, mtext
|
||||||
|
|
||||||
|
// General configuration
|
||||||
|
#show: exam.with(
|
||||||
|
title: "Exam",
|
||||||
|
|
||||||
|
course-title: [Abstract Binary Computation & Elegant Finite Graphs],
|
||||||
|
|
||||||
|
// Top left header
|
||||||
|
institution: [Super University],
|
||||||
|
|
||||||
|
// Top right header
|
||||||
|
course-short-title: "ABC & EFG",
|
||||||
|
date: "1. January 1970",
|
||||||
|
|
||||||
|
// Exam details, appears below the title
|
||||||
|
course-code: "π",
|
||||||
|
duration-minutes: "90",
|
||||||
|
|
||||||
|
// Configure personal information fields
|
||||||
|
ask-trainer-name: false,
|
||||||
|
ask-group: true,
|
||||||
|
ask-student-number: false,
|
||||||
|
|
||||||
|
// Configure language, either "de" or "en"
|
||||||
|
language: "en",
|
||||||
|
|
||||||
|
// custom instructions
|
||||||
|
// instructions: none,
|
||||||
|
|
||||||
|
// change font-size
|
||||||
|
// font-size: 12pt,
|
||||||
|
|
||||||
|
// custom paper size
|
||||||
|
// paper-size: "a4",
|
||||||
|
|
||||||
|
// custom numbering of exercises
|
||||||
|
// exercise-numbering: "1",
|
||||||
|
|
||||||
|
// custom filling lightness for headings
|
||||||
|
// fill-luma: 150,
|
||||||
|
)
|
||||||
|
|
||||||
|
// First exercise
|
||||||
|
#exercise("Relations and their properties", 2)
|
||||||
|
|
||||||
|
Consider the relation $R subset NN^2$, defined as follows:
|
||||||
|
|
||||||
|
$ (x, y) in R #h(0.5cm) <==> #h(0.5cm) x + y #mtext[is odd]. $
|
||||||
|
|
||||||
|
Is $R$ reflexive? transitive? symmetric? antisymmetric?
|
||||||
|
|
||||||
|
// Second exercise
|
||||||
|
#exercise("Properties of functions", 3)
|
||||||
|
|
||||||
|
Let $f : (0, +infinity) → (0, +infinity)$ with $f (x) = e^(-x)$.
|
||||||
|
|
||||||
|
// Several sub-questions
|
||||||
|
#exercise-items((
|
||||||
|
(1, [Is $f$ injective?]), // The first argument is the number
|
||||||
|
// of points the item is worth
|
||||||
|
(1, [Is $f$ surjective?]),
|
||||||
|
(2, [Is $f$ bijective?]),
|
||||||
|
))
|
||||||
|
|
||||||
|
// Third exercise
|
||||||
|
#exercise("Logical operators", 3.14)
|
||||||
|
|
||||||
|
Consider the following truthtable:
|
||||||
|
|
||||||
|
#align(center, table(
|
||||||
|
stroke: horiz-only(0.5pt),
|
||||||
|
columns: (auto, auto, auto),
|
||||||
|
align: center,
|
||||||
|
[$A$], [$B$], [$A or B$],
|
||||||
|
[1], [1], [1],
|
||||||
|
[1], [0], [1],
|
||||||
|
[0], [1], [1],
|
||||||
|
[0], [0], [1],
|
||||||
|
))
|
||||||
|
|
||||||
|
// More sub-questions
|
||||||
|
// override-points to false, so that the exercise total points are not recomputed.
|
||||||
|
#exercise-items(override-points: false, numbering: "i)", (
|
||||||
|
(1, [Is the truthtable correct?]),
|
||||||
|
(1, [If not, fix it.]),
|
||||||
|
))
|
||||||
|
|
||||||
BIN
thumbnail.png
Normal file
BIN
thumbnail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
10
typst.toml
10
typst.toml
|
|
@ -1,8 +1,14 @@
|
||||||
[package]
|
[package]
|
||||||
name = "exam"
|
name = "exam"
|
||||||
version = "0.1.0"
|
version = "0.3.0"
|
||||||
entrypoint = "lib.typ"
|
entrypoint = "src/lib.typ"
|
||||||
authors = ["Gaspard Jankowiak"]
|
authors = ["Gaspard Jankowiak"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Exams for NAWI Graz."
|
description = "Exams for NAWI Graz."
|
||||||
compiler = "0.14.0"
|
compiler = "0.14.0"
|
||||||
|
categories = ["layout", "office"]
|
||||||
|
|
||||||
|
[template]
|
||||||
|
path = "template"
|
||||||
|
entrypoint = "exam.typ"
|
||||||
|
thumbnail = "thumbnail.png"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue