added logic tables

This commit is contained in:
Louis Heredero 2024-07-01 10:56:29 +02:00
parent c33c055b29
commit b93db17806
Signed by: HEL
GPG Key ID: 8D83DE470F8544E7
3 changed files with 192 additions and 0 deletions

BIN
gallery/example5.pdf Normal file

Binary file not shown.

27
gallery/example5.typ Normal file
View File

@ -0,0 +1,27 @@
#import "/src/logic.typ": *
#let F = $((not p -> q) or r) -> ((p and not r) or not q)$
#let F = _implies(
_or(
_implies(
_not("p"),
"q"
),
"r"
),
_or(
_and(
"p",
_not("r")
),
_not("q")
)
)
#display(F)
#get-atoms(F)
#get-components(F)
#{
set text(size: 0.8em)
truth-table(F)
}

165
src/logic.typ Normal file
View File

@ -0,0 +1,165 @@
#let _and(a, b) = (
type: "and",
a: a,
b: b
)
#let _or(a, b) = (
type: "or",
a: a,
b: b
)
#let _implies(a, b) = (
type: "implies",
a: a,
b: b
)
#let _not(a) = (
type: "not",
a: a
)
#let display(formula, first: true) = {
let display = display.with(first: false)
if type(formula) == str {
return formula
}
if type(formula) != dictionary {
panic("Invalid type")
}
let res = none
if formula.type == "and" {
res = $#display(formula.a) and #display(formula.b)$
} else if formula.type == "or" {
res = $#display(formula.a) or #display(formula.b)$
} else if formula.type == "implies" {
res = $#display(formula.a) -> #display(formula.b)$
} else if formula.type == "not" {
res = $not #display(formula.a)$
} else {
panic("Invalid type")
}
if not first and formula.type != "not" {
res = $(#res)$
}
return res
}
#let _walk(formula, callback) = {
let result = ()
if type(formula) == str {
result += callback(formula, result)
} else if type(formula) == dictionary {
result += callback(formula, result)
if formula.type in ("and", "or", "implies") {
result += _walk(formula.a, callback)
result += _walk(formula.b, callback)
} else if formula.type == "not" {
result += _walk(formula.a, callback)
}
}
return result
}
#let evaluate(formula, atoms-values) = {
if type(formula) == str {
return atoms-values.at(formula)
}
if type(formula) != dictionary {
panic("Invalid type")
}
if formula.type == "not" {
return 1 - evaluate(formula.a, atoms-values)
}
if not formula.type in ("and", "or", "implies") {
panic("Invalid type")
}
let a = evaluate(formula.a, atoms-values)
let b = evaluate(formula.b, atoms-values)
let cond = false
if formula.type == "and" {
cond = (a == 1 and b == 1)
} else if formula.type == "or" {
cond = (a == 1 or b == 1)
} else if formula.type == "implies" {
cond = (a == 0 or b == 1)
}
return if cond {1} else {0}
}
#let get-atoms(formula) = {
return _walk(formula, (a, res) => {
if type(a) == str {
res.push(a)
}
return res
}).dedup()
}
#let get-components(formula) = {
return _walk(formula, (a, res) => {
res.push(a)
return res
}).dedup()
}
#let get-level(formula) = {
if type(formula) == str {
return 0
}
if type(formula) != dictionary {
panic("Invalid type")
}
if formula.type in ("and", "or", "implies") {
return calc.max(
get-level(formula.a),
get-level(formula.b)
) + 1
}
if formula.type == "not" {
return get-level(formula.a) + 1
}
panic("Invalid type")
}
#let truth-table(formula) = {
let atoms = get-atoms(formula).sorted()
let comps = get-components(formula)
let comps-lvls = comps.map(c => (c, get-level(c)))
let sorted = comps-lvls.sorted(key: cl => cl.last())
let cells = ()
let cur-lvl = 0
for (i, (comp, lvl)) in sorted.enumerate() {
if lvl != cur-lvl {
cells.push(table.vline())
} else if i != 0 {
cells.push(table.vline(stroke: (dash: "dashed")))
}
cur-lvl = lvl
cells.push(display(comp))
}
cells.push(table.hline())
for i in range(calc.pow(2, atoms.len())) {
if calc.rem(i, 4) == 0 and i != 0 {
cells.push(table.hline())
}
let atoms-values = (:)
for (j, a) in atoms.enumerate() {
let bit = i.bit-rshift(atoms.len() - j - 1).bit-and(1)
atoms-values.insert(a, bit)
}
for (comp, lvl) in sorted {
let value = evaluate(comp, atoms-values)
cells.push($#value$)
}
}
table(
columns: sorted.len(),
stroke: none,
align: center + horizon,
inset: (left: 0.5em, right: 0.5em, top: 0.4em, bottom: 0.4em),
..cells
)
}