- 开发工具
- 数据可视化
【免费下载链接】penrose
Create beautiful diagrams just by typing notation in plain text.
本文围绕 packages/examples/src/mobius 示例展开,讲解 Penrose 如何通过.domain、.substance、.style三个声明式文件完整刻画"圆与点的反演变换"(circle inversion,Möbius 变换的一种)并渲染出带渐变光影质感的矢量图。读完本文,你将掌握 Penrose 三段式建模中构造函数(constructor)、条件式样式匹配(forall ... where)与数值约束(ensure)的配合方法,并能独立读懂并复刻一个含自定义数学推导的完整示例。
示例定位与文件结构
该目录的 README.md 只有一句话点明主题:这是一个用于**演示反演变换(inversion transformation)**的 Domain,当前仅支持圆(circles)和点(points),直线、圆弧等其他形状可能在后续加入。围绕这一简短描述,目录内还配套了四个关键文件,共同构成一个可直接运行的 Penrose 示例:
| 文件 | 作用 |
|---|---|
| mobius.domain | 类型系统:定义Center、Shape、Point、Circle及反演构造函数Inversion |
| mobius.substance | 程序声明:实例化一个反演中心、三个圆及其反演结果,并附加 LaTeX 标签 |
| mobius.style | 样式与数学:把反演公式写成可优化的数值计算,同时定义画布、图像纹理、约束与图层 |
| mobius.trio.json | 运行清单:关联三份文件并固定随机种子variation |
在示例注册表 registry.json 中,该示例登记名为"Mobius Transformation of Circles",且"gallery": true,意味着它会被收录进在线画廊,属于官方推荐展示的示例之一。
Domain 层:用类型与构造函数声明"反演"这一数学对象
mobius.domain 全文仅 10 行,却完整定义了本示例的全部抽象:
type Center type Shape type Point type Circle Point <: Shape Circle <: Shape constructor Inversion( Shape original, Center center ) -> Shape关键点逐条拆解:
- 类型层级:
Point与Circle都继承自Shape,因此凡是对Shape编写的样式规则,会自动作用于点和圆,这是 Penrose 面向对象式类型继承的典型用法。 Center独立成类:反演中心(即变换圆心与半径所在)被单独建模,不并入Shape,便于在样式层为其单独绘制虚线参考圆与中心点。- 构造函数
Inversion( Shape original, Center center ) -> Shape:这是整个示例的核心抽象。它声明"对一个 Shape 施加以 Center 为参考的反演,结果仍是一个 Shape"。注意其结果类型是抽象父类Shape,意味着既可以反演Point,也可以反演Circle——Substance 中恰好同时使用了这两种用法,而 Style 层则通过匹配s := Inversion(p, r)与s := Inversion(c, r)两条规则分别给出不同的几何算法。
这种"用构造函数承载数学运算"的建模方式,是 Penrose 把数学概念翻译成可视化程序的典型路径:Domain 只描述"有什么对象和关系",具体怎么算、怎么画,全部留给 Style 层。
Substance 层:声明对象并调用构造函数
mobius.substance 声明了一个反演中心与三个待反演的圆:
Center r Circle c1, c2, c3 Shape ic1 := Inversion(c1, r) Shape ic2 := Inversion(c2, r) Shape ic3 := Inversion(c3, r)- 语法
Shape ic1 := Inversion(c1, r)表示用构造函数创建对象并命名,ic1、ic2、ic3即三个圆的反演像。 - 三组
Label语句为对象绑定数学排版标签,标签内容使用 LaTeX 书写(Penrose 会将它们渲染为排版公式):
Label r $S^1$ Label c1 $c_1$ Label c2 $c_2$ Label c3 $c_3$ Label ic1 $c'_1$ Label ic2 $c'_2$ Label ic3 $c'_3$这里$S^1$表明反演参考圆是单位圆,c'_i与c_i的撇号记法清晰区分了原圆与其反演像,正好呼应"反演圆"的主题语义。
Style 层:把反演公式写成可优化计算
mobius.style 是本示例最富技术含量的部分,它包含画布设置、图像纹理、几何算法、布局约束与图层管理五类内容。
画布与背景纹理
canvas { width = 800 height = 700 } global { shape background = Image { href: "background.svg" center: (0,0) width: canvas.width height: canvas.height opacity: .6 preserveAspectRatio: "none" } }画布固定为 800×700,背景直接引用同目录下的 background.svg(一段木纹色渐变矩形)作为整幅图的底层纹理,opacity: .6降低饱和度以避免干扰前景图形。
通用规则:给所有带标签的形状绘制公式
forall Shape s where s has label { shape s.eq = Equation { center: s.center string: s.label fontSize: "25px" } }forall ... where ...是 Penrose 的条件匹配语法:任何"拥有标签的 Shape"都会生成一个Equation形状,把 LaTeX 标签渲染在自身中心。由于Point <: Shape、Circle <: Shape,这条规则自动覆盖圆及其反演像,一处声明、处处生效。
反演中心Center的绘制
forall Center c { vec2 c.center = (?, ?) scalar c.r = ? shape c.circle = Circle { center: c.center r: c.r fillColor: rgba(0, 0, 0, 0) strokeWidth: 2 strokeStyle: "dashed" strokeColor: rgba(0, 0, 0, 1) } shape c.point = Circle { center: c.center fillColor: rgba(0, 0, 0, 1) r: 3 } ensure lessThan(100, c.r) }(?, ?)表示该数值由 Penrose 的优化求解器(基于自动微分的能量优化)在布局阶段自动求解,这正是 Penrose 区别于普通绘图库的核心机制。参考圆以透明填充 + 黑色虚线描边呈现,圆心以半径 3 的实心小黑点标出,并施加约束ensure lessThan(100, c.r)保证参考圆半径足够大。
原圆的绘制与球面光影
forall Circle c { vec2 c.center = (?, ?) scalar c.r = ? shape c.circle = Circle { center: c.center r: c.r } ensure lessThan(30, c.r) shape c.shading = Image { href: "sphere-shading.svg" center: c.center width: 2*c.r height: 2*c.r ensureOnCanvas: false opacity: .5 } layer c.circle above c.shading }除了常规圆,这里给每个原圆叠加了同目录 sphere-shading.svg(一幅径向黑白渐变)作为球体光影贴图,尺寸跟随圆的直径2*c.r,透明度 0.5,并通过layer c.circle above c.shading让圆轮廓位于光影之上。ensureOnCanvas: false允许贴图超出画布边界而不触发约束。
点反演公式(核心数学之一)
forall Shape s; Point p; Center r where s := Inversion( p, r ) { scalar s.d2 = vdistsq(p.pos, r.center) scalar s.coef = r.r * r.r / s.d2 vec2 s.pos = r.center + s.coef * (p.pos - r.center) shape s.point = Circle { r: 5 fillColor: p.point.fillColor center: s.pos } }这里完整实现了反演的标准解析公式。对参考圆半径r.r、圆心r.center,点p.pos的反演像为:
s.pos = r.center + (r.r² / |p.pos − r.center|²) · (p.pos − r.center)vdistsq计算两点距离平方(避免开方),s.d2即分母|P − C|²;s.coef = r.r * r.r / s.d2即缩放系数r²/d²;- 结果点
s.pos用半径 5 的小圆绘制,并继承原点的填充色。
vdistsq与vdist都是 Penrose 内置的自动微分运算原语,见 Autodiff.ts——它们不仅计算数值,还会为优化求解器提供可微的梯度信息,这是"用约束反推布局"的底层保障。
圆反演公式(核心数学之二)
圆的反演不像点那样能一步到位,mobius.style 采用了"取两端点、分别反演、再求中点和半径"的标准算法:
scalar s.d = vdist( c.center, r.center ) scalar s.coef1 = ( s.d - c.r ) / s.d vec2 s.p1 = r.center + s.coef1 * (c.center - r.center) scalar s.i1coef = r.r * r.r / vdistsq( r.center, s.p1 ) vec2 s.ip1 = r.center + s.i1coef * ( s.p1 - r.center) scalar s.coef2 = (s.d + c.r) / s.d vec2 s.p2 = r.center + s.coef2 * (c.center - r.center) scalar s.i2coef = r.r * r.r / vdistsq( r.center, s.p2 ) vec2 s.ip2 = r.center + s.i2coef * ( s.p2 - r.center ) scalar s.r = vdist(s.ip1, s.ip2) / 2 vec2 s.center = 0.5 * (s.ip1 + s.ip2)算法逻辑分四步:
- 取直径两端点:沿两圆心连线方向,取原圆上距参考圆心最近的点
p1与最远的点p2(系数(d−r)/d与(d+r)/d); - 分别反演两端点:对
p1、p2各自套用点反演公式,得到ip1、ip2; - 反演圆的圆心:为两端点反演像的中点
0.5 * (ip1 + ip2); - 反演圆的半径:为两端点反演像距离的一半
vdist(ip1, ip2) / 2。
随后把算出的圆绘制出来,同样叠加球面光影贴图、以layer控制层级,并施加ensure lessThan(30, s.r)防止反演圆过大。这套推导正是"圆在反演下的像仍是圆(圆心在参考圆上时退化为直线)"这一几何性质的数值实现。
布局约束与图层管理
示例末尾集中声明了几组约束与图层规则:
forall Circle c1; Circle c2 { ensure touching(c1.circle, c2.circle) } forall Center r; Circle c { ensure contains(r.circle, c.circle, 30) } forall Shape s1; Shape s2 { s1.eq above s2.circle s1.eq above s1.circle s2.eq above s1.circle s2.eq above s2.circle }ensure touching(c1.circle, c2.circle):任意两个原圆彼此相切,让画面紧凑有序;ensure contains(r.circle, c.circle, 30):参考圆以 30 像素余量包住每个原圆,保证反演几何关系在视觉上成立;- 四条
above规则统一确保所有公式标签始终绘制在圆之上,避免文字被图形遮挡。
这些touching、contains、lessThan都是 Penrose 内置的几何约束函数,其实现位于 Constraints.ts(lessThan见 L72、touching见 L291、contains见 L315)。它们会被编译成能量项交给优化器最小化,最终同时满足全部约束的布局即为渲染结果。
运行配置:trio.json 与随机种子
mobius.trio.json 把三段式程序串成一个可运行单元:
{ "substance": "./mobius.substance", "style": ["./mobius.style"], "domain": "./mobius.domain", "variation": "HibernationPony6425" }style是数组,说明一个示例可以叠加多个样式文件(本示例只用了一个);variation固定随机种子"HibernationPony6425",保证每次运行得到确定性的初始布局——这对示例复现与画廊截图尤为重要。
该示例位于packages/examples/src下,因此会被示例构建流程扫描进 registry.json,并以 "Mobius Transformation of Circles" 的名称出现在在线示例集中。如需本地运行,可按仓库根目录 package.json 中约定的脚本在packages/examples下执行相应的示例编译命令,产出 SVG 渲染结果。
从源码理解:ensureOnCanvas与自动微分如何支撑本示例
本示例大量使用"数值未知、由约束求解"的写法((?, ?)与scalar c.r = ?),其底层依赖两点:
ensureOnCanvas属性:样式里对两张 SVG 贴图显式设置了ensureOnCanvas: false。在编译器端,该布尔属性由 Style.ts 读取并决定是否为形状生成"必须在画布内"的约束;同时 AttrHelper.ts 将其列入"不自动映射为 SVG 属性"的白名单,避免该配置泄漏到最终 SVG 标签中。默认值则在 Circle.ts 等处定义为true——因此示例中主动关掉它,是让贴图可以自由超出画布的刻意设计。vdist/vdistsq的自动微分:点反演与圆反演公式中的所有vdist、vdistsq调用,最终都会经过 Autodiff.ts 注册的可微运算。Penrose 据此把"反演结果应满足的几何关系"翻译为连续可导的能量函数,由优化器(如 Lbfgs.ts)迭代求解出c.center、c.r等所有自由变量。这也解释了为什么 Substance 中从未指定任何坐标——位置与大小全部是"被约束出来的"。
扩展方向:从圆与点到直线与圆弧
README 末尾提到"直线、圆弧或其他形状可能后续加入"(lines/arcs or other shapes may be added later)。从现有代码结构看,接入新形状的路径相当清晰:
- 在 mobius.domain 中新增
type Line/type Arc并继承Shape; - 在 Substance 中创建反演实例
Shape il := Inversion(line, r); - 在 mobius.style 中新增
forall Shape s; Line l; Center r where s := Inversion(l, r)规则块,利用"过参考圆心的直线反演为自身、不过圆心的直线反演为圆"等性质补充对应算法。
这一"类型 + 构造函数 + 条件样式"的三层扩展模式,正是 Penrose 将任意数学变换可视化的通用方法论,也是该示例作为教学范例的价值所在。
- 开发工具
- 数据可视化
【免费下载链接】penrose
Create beautiful diagrams just by typing notation in plain text.
相关推荐
Penrose 入门实战:用 `.domain` / `.substance` / `.style` 三文件绘制你的第一张集合图
Penrose 入门实战:用 .domain / .substance / .style 三文件绘制你的第一张集合图 这篇教程带你完成 Penrose 中的第一
开发工具数据可视化Penrose 实战指南:用 Domain–Substance–Style 三语言纯文本绘制集合论欧拉图
Penrose 实战指南:用 Domain–Substance–Style 三语言纯文本绘制集合论欧拉图 Penrose 是一个"只靠纯文本符号就能生成高质量图
开发工具数据可视化Penrose 三种 DSL 文法全解:Domain、Substance 与 Style 的正式语法与解析实现
Penrose 三种 DSL 文法全解:Domain、Substance 与 Style 的正式语法与解析实现 Penrose 通过三种相互协作的领域特定语言(
开发工具数据可视化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考