feat: add document conversion templates

Add md-to-pdf and md-to-docx templates for mytoolkit convert command.
Migrated from workspace/templates.
This commit is contained in:
Zhengshou Lai
2026-05-06 00:11:37 +08:00
parent dea83774a5
commit 2791a785ff
18 changed files with 20018 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
---
title: 计算力学导论
author: 赖正首
lang: zh
date: 2025
---
# 引言
计算力学是利用数值方法求解力学问题的学科。本章将介绍有限元方法的基本概念。
## 历史背景
有限元方法起源于 20 世纪 50 年代的航空工业。Turner 等人于 1956 年首次发表了关于有限元方法的系统性论文。
## 本章概述
本书将按以下结构组织:
- **第一章** 介绍有限元方法的基本原理
- **第二章** 讨论变分原理与弱形式
- **第三章** 探讨高阶有限元与谱方法
---
# 有限元方法基础
## 强形式与弱形式
考虑一维泊松方程:
$$-u''(x) = f(x), \quad x \in (0, 1)$$
边界条件为 $u(0) = u(1) = 0$。
以下定理框使用 Typst 语法;`mytoolkit` 会在转 Typst 前自动包一层 raw,避免行首 `#` 被 Pandoc 转义。
#theorem(title: "解的存在唯一性")[
设 $f \in L^2(0,1)$,则上述边值问题存在唯一弱解 $u \in H_0^1(0,1)$。
]
#proof[
根据 Lax-Milgram 定理,双线性形式 $a(u, v) = \int_0^1 u' v' \, \mathrm{d}x$ 在 $H_0^1(0,1)$ 上是连续且强制的,线性泛函 $L(v) = \int_0^1 f v \, \mathrm{d}x$ 是连续的。因此存在唯一解。
]
## 伽辽金方法
#example(title: "一维泊松方程")[
取试探空间 $V_h = \mathrm{span}\{\phi_1, \phi_2, \dots, \phi_n\}$,其中 $\phi_i$ 为分段线性帽函数。则近似解可写为:
$$u_h(x) = \sum_{j=1}^n u_j \phi_j(x)$$
代入弱形式并取测试函数 $v = \phi_i$,得到线性方程组 $K u = F$。
]
---
# 变分原理
## 极小位能原理
#lemma(title: "能量泛函的凸性")[
定义能量泛函 $J(v) = \frac{1}{2} a(v, v) - L(v)$,则 $J$ 是严格凸的。
]
## 拉格朗日乘子法
对于有约束的优化问题,引入拉格朗日乘子:
$$\mathcal{L}(u, \lambda) = J(u) + \lambda^T (B u - g)$$
---
# 高级主题
## 代码示例
以下是用 Python 实现的简单有限元求解器:
```python
import numpy as np
from scipy.sparse import csr_matrix
from scipy.sparse.linalg import spsolve
def assemble_stiffness(n):
"""组装一维刚度矩阵"""
h = 1.0 / (n + 1)
data = np.array([1/h] * (n-1) + [2/h] * n + [1/h] * (n-1))
rows = np.array(list(range(n-1)) + list(range(n)) + list(range(1, n)))
cols = np.array(list(range(1, n)) + list(range(n)) + list(range(n-1)))
return csr_matrix((data, (rows, cols)), shape=(n, n))
def solve_poisson_1d(f, n):
"""求解一维泊松方程"""
K = assemble_stiffness(n)
h = 1.0 / (n + 1)
F = h * f(np.linspace(h, 1-h, n))
return spsolve(K, F)
```
## 练习
#exercise(title: "3.1")[
证明一维线性有限元方法的收敛阶为 $O(h^2)$,即:
$$|u - u_h|_1 \leq C h |u|_2$$
其中 $C$ 为与 $h$ 无关的常数。
]
#exercise(title: "3.2")[
编写程序计算二维正方形区域上的泊松方程,并与解析解比较收敛阶。
]
+194
View File
@@ -0,0 +1,194 @@
// 教材模板 —— 定理/代码/目录 + 封面页 + 三线表
#import "@preview/bookly:3.1.0": *
#import "@preview/great-theorems:0.1.2": *
#import "@preview/codly:1.3.0": *
#import "@preview/showybox:2.0.4": showybox
#let primary = rgb("#1F4E79")
#let secondary = rgb("#2C3E50")
#let accent = rgb("#3467A8")
#let light = rgb("#96A0AA")
#let rule-blue = accent.lighten(46%)
// Filled mathblocks: inset so fill does not hug text; radius slightly larger than codly (4pt)
#let _mathblock-inset = (x: 14pt, y: 12pt)
#let _mathblock-radius = 5pt
// Theorem-like environments (great-theorems)
#let theorem = mathblock(
blocktitle: "Theorem",
fill: rgb("#E8F0FE"),
inset: _mathblock-inset,
radius: _mathblock-radius,
)
#let lemma = mathblock(
blocktitle: "Lemma",
fill: rgb("#F3E8FF"),
inset: _mathblock-inset,
radius: _mathblock-radius,
)
#let proof = proofblock(
inset: _mathblock-inset,
radius: _mathblock-radius,
)
#let example = mathblock(
blocktitle: "Example",
fill: rgb("#F0FDF4"),
inset: _mathblock-inset,
radius: _mathblock-radius,
)
// Uncounted block: titles must use `title: ...`, not a positional string (see great-theorems README).
#let exercise = mathblock(
blocktitle: "Exercise",
prefix: [],
titlix: title => [*习题*~#title~#h(0.25em)],
fill: rgb("#FEF3C7"),
inset: (x: 14pt, y: 13pt),
radius: _mathblock-radius,
)
#let cover-page(title, subtitle, date) = {
page(margin: (top: 3cm, bottom: 3cm, left: 3cm, right: 3cm))[
#align(center + horizon)[
#v(2cm)
#text(size: 28pt, weight: "bold", fill: primary, title)
#if subtitle != none {
v(0.8cm)
text(size: 16pt, fill: accent, subtitle)
}
#if date != none {
v(2cm)
text(size: 12pt, fill: light, date)
}
]
]
}
#let three-line-table(..args) = {
let kwargs = args.named()
let children = args.pos()
let new_children = ()
let header_seen = false
for child in children {
if child.func() == table.header {
new_children.push(table.hline(stroke: 1.25pt + rule-blue))
new_children.push(child)
header_seen = true
} else if header_seen and child.func() == table.hline {
new_children.push(table.hline(stroke: 0.75pt + rule-blue))
header_seen = false
} else {
new_children.push(child)
}
}
new_children.push(table.hline(stroke: 1.25pt + rule-blue))
kwargs.stroke = none
kwargs.inset = (x: 8pt, y: 5pt)
table(..new_children, ..kwargs)
}
#let textbook(
title: none,
subtitle: none,
date: none,
body,
) = {
if title != none {
cover-page(title, subtitle, date)
pagebreak()
}
set text(
font: (
"Cambria",
"PingFang SC",
),
size: 11pt,
lang: "zh",
)
show raw: set text(font: (
"Courier New",
"Menlo",
"Monaco",
"SimSun",
))
// Long-form reading: most generous body rhythm in this family
set par(
justify: true,
leading: 0.86em,
spacing: 1.2em,
first-line-indent: 2em,
)
show: codly-init
codly(
stroke: 0.5pt + rule-blue,
fill: rgb("#F8F9FA"),
zebra-fill: none,
lang-stroke: none,
lang-fill: light.lighten(60%),
radius: 4pt,
)
show: great-theorems-init
set heading(numbering: "1.1")
// Same 11pt body as default: match list rhythm to default template
set list(indent: 1.2em, body-indent: 0.45em, spacing: 0.62em)
set enum(indent: 1.2em, body-indent: 0.45em, spacing: 0.62em)
// Vertical rhythm matches manual (long-form); font sizes differ
show heading.where(level: 1): it => {
v(1.45em)
text(size: 18pt, weight: "bold", fill: primary, it)
v(0.55em)
}
show heading.where(level: 2): it => {
v(1.1em)
text(size: 14pt, weight: "bold", fill: accent, it)
v(0.5em)
}
show heading.where(level: 3): it => {
v(0.8em)
text(size: 12pt, weight: "bold", fill: secondary, it)
v(0.4em)
}
set page(
paper: "a4",
margin: (top: 2.5cm, bottom: 2.5cm, left: 2.5cm, right: 2.5cm),
header: context {
let h = query(selector(heading.where(level: 1))).find(it => it.location().page() == here().page())
if h != none {
align(right)[
#text(size: 9pt, fill: light)[
#h.body
]
]
}
},
footer: context {
align(center)[
#text(size: 9pt, fill: light)[
#counter(page).display("1")
]
]
},
)
show link: it => text(fill: accent, it)
outline(title: "目录", depth: 2)
pagebreak()
body
}
File diff suppressed because it is too large Load Diff