<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Yi's Blog</title><id>https://ycao.net/</id><link rel="self" href="https://ycao.net/atom.xml"/><link rel="alternate" href="https://ycao.net/"/><updated>2026-06-16T00:00:00Z</updated><author><name>Yi Cao</name></author><entry><title>LLM and (Math) Education</title><link rel="alternate" href="https://ycao.net/posts/ai-and-education.html"/><id>https://ycao.net/posts/ai-and-education.html</id><updated>2026-06-16T00:00:00Z</updated><content type="html">&lt;p&gt;&lt;i&gt;Abstract: This article empirically asserts a lower bound of human effort required
to learn a mathematical (or any other abstraction-heavy subject) regardless of LLM.&lt;/i&gt;&lt;/p&gt;&lt;p&gt;TODO&lt;/p&gt;</content></entry><entry><title>三年LLM：过去和未来</title><link rel="alternate" href="https://ycao.net/posts/llm-after-three-years.html"/><id>https://ycao.net/posts/llm-after-three-years.html</id><updated>2026-03-27T00:00:00Z</updated><content type="html">&lt;p&gt;OpenAI的ChatGPT于2022年年底发布。得益于境外手机号接码渠道，我第一时间就用上并惊叹于人工智能的突破。一转眼，LLM在过去的三年里已无可辩驳地渗透至千家万户。&lt;/p&gt;&lt;h2 id="history"&gt;一小段历史&lt;/h2&gt;&lt;p&gt;Transformer模型最早于2017年由谷歌提出，核心思路是通过Self-Attention机制显式地表达词语之间语义联系&lt;sup&gt;&lt;a href="#cite-vaswaniAttentionAllYou2017"&gt;[1]&lt;/a&gt;&lt;/sup&gt;。OpenAI在2018年首次发布GPT-1模型，将架构简化为Decoder-only的Autoregressive词语预测&lt;sup&gt;&lt;a href="#cite-radfordImprovingLanguageUnderstanding2018"&gt;[2]&lt;/a&gt;&lt;/sup&gt;。自此，LLM的总体架构尘埃落定，后续的GPT-2、GPT-3等皆在此基础上扩展模型大小和数据集的规模。不仅局限于文字，Transformer在视觉领域表现也很好&lt;sup&gt;&lt;a href="#cite-kolesnikovImageWorth16x162021"&gt;[3]&lt;/a&gt;&lt;/sup&gt;。为何大模型能够&lt;i&gt;涌现&lt;/i&gt;出强大的能力仍不得而知。Anthropic于2021年在一个极度简化的模型中发现了Induction Heads结构&lt;sup&gt;&lt;a href="#cite-geshkovskiMathematicalPerspectiveTransformers2025"&gt;[4]&lt;/a&gt;&lt;/sup&gt;，但如同生物学第一次发现红细胞，我们离真正理解LLM的工作原理相差甚远。&lt;/p&gt;&lt;h2 id="my-experience"&gt;我的经历&lt;/h2&gt;&lt;p&gt;&lt;b&gt;朴素文本生成与搜索引擎&lt;/b&gt;&lt;/p&gt;&lt;p&gt;记忆中，初代GPT-3.5尚且幻觉频发，其输出往往需人工核查事实。那些本身不具严肃意义、亦无需严格验证的作文任务，最宜交由GPT处理，这也算是回归了其文本续写的本质功能。&lt;/p&gt;&lt;p&gt;GPT在联网后实用性显著提升。比起传统搜索引擎，GPT的优势在于：其一，迅速地总结和提炼信息。谷歌在搜索引擎中嵌入了AI Overview，实际体验确实更加方便；其二，也是最重要的——解决了搜索引擎“不知道搜什么关键字”的问题。比如我想知道“3D游戏中角色自适应动画如何实现”，LLM会直接告诉我我应该搜索“Inverse Kinematics”。这是传统基于关键词的搜索引擎难以企及的。&lt;/p&gt;&lt;p&gt;&lt;b&gt;LLM即老师&lt;/b&gt;&lt;/p&gt;&lt;p&gt;LLM在教学方面潜力巨大。教学任务与LLM高度契合：不仅模型本身具备大量先验的初等知识（训练集中反复），教材也能提供充足的上下文，学生的问题大多也是小规模良定义的，只需要LLM重新解释一遍即可。&lt;/p&gt;&lt;p&gt;常规问答必不多言。拿到Claude Code后，我试着将哈佛的&lt;a href="https://nlp.seas.harvard.edu/annotated-transformer"&gt;The Annotated Transformer&lt;/a&gt;和Karpathy的&lt;a href="https://github.com/karpathy/nanoGPT"&gt;nanoGPT&lt;/a&gt;投喂进Claude Code，令其对照两份材料，撰写一份从零构建GPT的教程。&lt;a href="https://github.com/yikerman/learn-attn/commit/8864b8dacb70b4219de6efe951064e46b5d6b7b2"&gt;第一版&lt;/a&gt;完成度尚可，但是作为教材未能妥善处理读者的阅读顺序和知识背景。我顺序阅读并学习，遇见觉得不通顺的地方就给予明确指示如何重写，最终两天完成一份在我认为&lt;a href="https://github.com/yikerman/learn-attn/blob/b193e0b5b687d35e9fc825e85b66c75462b899ca/learn/babygpt-tutorial.pdf"&gt;质量不错的教程&lt;/a&gt;。可见虽LLM仍需人类引导，效率提升非常显著。&lt;/p&gt;&lt;p&gt;若使用不当，LLM也会带来意料之中的麻烦——倘若只知复制粘贴，知识终究未曾过脑&lt;sup&gt;&lt;a href="#cite-kosmynaYourBrainChatGPT2025"&gt;[5]&lt;/a&gt;&lt;/sup&gt;。這種成績，使人汗顏！（发自我的手机）&lt;/p&gt;&lt;p&gt;&lt;b&gt;多模态和Agent&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Agent概念虽被外界炒作得天花乱坠，思路其实非常朴素：在prompt中教LLM以固定的格式引用外部工具（如读取文件、爬取网页等），确定性代码结果加入LLM上下文。Agent刚被引入时看上去能大幅度提升LLM智力，究其根本还是提供了正确充分的上下文，与人类手动提供上下文（如将一个框架的文档粘贴进对话框）本质没有区别，Agent只是自动化了这个过程。程序员是Agent的最大受益者，因其需频繁与代码仓库交互。LLM代码能力已被多次探讨过，同样的，在缺乏显式引导下LLM喜欢写毫无设计且充斥着不必要冗余的代码。有软工水平的人可以通过LLM大幅提升效率，反之则大概率误入歧途。&lt;/p&gt;&lt;p&gt;多模态能力很有用，但相较于文字稍不稳定一些。我试图用&lt;a href="https://github.com/yikerman/math-notes/blob/90ac740f7d2509aa48f0bf4bef64d6b3ca8cd7e0/CLAUDE.md"&gt;Claude Code将我糟糕的手写数学笔记转化为整齐编排的&lt;span&gt;\(\LaTeX\)&lt;/span&gt;文档&lt;/a&gt;。效果相当好，文字几无人工干预需要，TikZ画图有大概50%成功率，大多需要人工调整。我注意到有些输出数学上正确，却和我原有笔记不一致。合理猜测LLM只是从图片大致概括得我的笔记内容，对于一些细节都采用了先验知识猜测和补全。&lt;/p&gt;&lt;h2 id="outlook"&gt;未来展望&lt;/h2&gt;&lt;p&gt;&lt;b&gt;AI存在泡沫吗？&lt;/b&gt;&lt;/p&gt;&lt;p&gt;LLM爆发后，两类产品尤为惹人生厌。其一，大公司为取悦短视股东，生硬地在产品中植入AI大粪，如Windows自带Copilot，臭不可闻。再者是一些&lt;a href="https://github.com/yikerman/awesome-ai-slop"&gt;小团队或者个人写的灾难级产品&lt;/a&gt;，哪怕是猪，站在风口上也能飞起来，荣誉提名OpenClaw。两者都证明了搞金融和做新闻的人非蠢即坏。&lt;/p&gt;&lt;p&gt;不仅如此，即使对有真才实学的AI公司（Google、OpenAI、Anthropic、阿里等，好歹是干实事的），盈利仍非常困难，OpenAI至今都在烧投资人的本金。高杠杆加上总体的经济停滞/下行趋势并不是一个好兆头。考虑到AI仍是有用的（不像前阵子Web3和Meta纯粹的骗局），我更倾向于AI会像互联网泡沫一样，大浪淘沙留下有价值的产品，只是中间泡沫破裂不知会软着陆还是硬着陆。&lt;/p&gt;&lt;p&gt;&lt;b&gt;LLM真的理解吗？&lt;/b&gt;&lt;/p&gt;&lt;p&gt;现有LLM智能已足够好，但和人类智能还有本质上的区别&lt;sup&gt;&lt;a href="#cite-dawidIntroductionLatentVariable2024"&gt;[6]&lt;/a&gt;&lt;/sup&gt;。从端到端的角度看，LLM只是概率拟合，无可避免的会给似是而非的东西，上下文对输出的影响&lt;i&gt;非常&lt;/i&gt;显著。早期Prompt Engineering就意图解决这个问题，尽管本身噱头大于技术。一部分，LLM听风是雨，对Prompt内容深信不疑。这点在自动化流程中比较灾难，如一个联网的LLM Agent在搜得网页中获取了错误信息，它会以看上去非常可信的方式总结和呈现。上下文也有更微妙的影响方式。LLM会尽力模仿上下文，后果是若代码里有隐藏的bug或不良的架构，LLM会继续沿用错误的方式，将问题越堆得积重难返。&lt;/p&gt;&lt;p&gt;简而言之：LLM的输出与输入强相关，且很多知识仍需后验提供。人类提供正确上下文和对大方向的“品味”反而更加的重要。&lt;/p&gt;&lt;p&gt;&lt;b&gt;大家都会失业吗？&lt;/b&gt;&lt;/p&gt;&lt;p&gt;据上述，LLM与人类显然仍具不少差距。更恰当的类比为，LLM是脑力劳动领域的纺织机——取代trivial的脑力劳动，如水文字。自然的，&lt;i&gt;若&lt;/i&gt;之后有全球范围的失业潮和经济下行，LLM充其量也只是引火线而已：总生产力提升了，为何生活水平反而下降？那必然是分配问题，大可不必因LLM感到存在危机。&lt;/p&gt;&lt;p&gt;有趣的副作用是，LLM祛魅了如教师、科学家、程序员等职业的神秘性。（并不是说这些职业不值得被尊重。且不必沮丧：AI能做是因为我们（在训练数据里）反复做过太多次，是时候休息一下了）新工具揭露了这些职业&lt;i&gt;大部分&lt;/i&gt;工作内容同样的trivial，在现有技术下易于自动化。坐办公室的白领再也没有理由看不起工厂的蓝领，大家本质都是讨辛苦饭的牛马打工人。&lt;/p&gt;&lt;p&gt;&lt;b&gt;LLM有瓶颈吗？&lt;/b&gt;&lt;/p&gt;&lt;p&gt;个人的猜测是LLM将会遇到瓶颈，原因是训练数据的劣化。互联网上愈发的充斥着LLM洗稿低质量内容，之后的LLM训练要么不引入新的数据，要么会逐渐退化为蒸馏前代LLM。但蒸馏只是面向小模型的特化技巧，用劣化的数据训练模型只能得到劣化的结果&lt;sup&gt;&lt;a href="#cite-shumailovAIModelsCollapse2024"&gt;[7]&lt;/a&gt;&lt;/sup&gt;。模型本身的架构上限尚不明确，毕竟人类连Transformer为何涌现出如此多的智能还不清楚。&lt;/p&gt;&lt;p&gt;我还有一些闲杂的思绪：近来有很多试图拓展LLM上下文的研究。但仔细审视人类的记忆和思维，短时工作记忆并没有那么高，更重要的能力是抽象和直觉——一个抽象中（如一个数学公理）下层细节对我是透明（transparent）的，我只需要知道条件和结果之间的关系，并一定程度依靠直觉判断何时调用哪种抽象来解决问题。&lt;/p&gt;</content></entry><entry><title>Color Recreation from First Principles</title><link rel="alternate" href="https://ycao.net/posts/recreating-color-simplified.html"/><id>https://ycao.net/posts/recreating-color-simplified.html</id><updated>2025-12-07T00:00:00Z</updated><content type="html">&lt;p&gt;&lt;i&gt;Abstract: This article provides a gentle derivation showing the
existence of a simple, measurable linear relationship between the LMS color
model as in human vision and RAW camera sensor data and the RGB values as
in displays and jpg/png/etc. images.&lt;/i&gt;&lt;/p&gt;&lt;p&gt;Real world colors are continuous spectra, such as the
&lt;a href="https://en.wikipedia.org/wiki/Sunlight#Composition_and_power"&gt;sunlight spectrum&lt;/a&gt;.
We can describe it as a continuous function &lt;span&gt;\(J(\lambda)\)&lt;/span&gt; where
&lt;span&gt;\(\lambda\)&lt;/span&gt; is the wavelength and &lt;span&gt;\(J(\lambda)\)&lt;/span&gt; is the intensity at
that wavelength.&lt;/p&gt;&lt;p&gt;Human eyes have three types of color receptors (cone cells) that are
&lt;a href="https://en.wikipedia.org/wiki/LMS_color_space"&gt;sensitive to different ranges of wavelengths&lt;/a&gt;,
named L, M, S for long, medium and short wavelengths respectively, loosely
corresponding to red, green and blue colors. Represent the responsiveness
of these three types of cells as functions &lt;span&gt;\(s(\lambda)\)&lt;/span&gt; at wavelength
&lt;span&gt;\(\lambda\)&lt;/span&gt;, then the perceived intensity of a type of cone cell can be
expressed as (take L as an example):&lt;/p&gt;&lt;div class="equation"&gt;\[L = \int_{-\infty}^\infty J(\lambda) s_L(\lambda) d\lambda\]&lt;/div&gt;&lt;p&gt;and same goes for M and S cells. As long as
&lt;span&gt;\(\begin{bmatrix} L &amp;amp; M &amp;amp; S \end{bmatrix}\)&lt;/span&gt; are the same, the perception
will be the same. It is significant not only because it is the basis of
human color vision, but also because camera sensors, utilizing
&lt;a href="https://en.wikipedia.org/wiki/Bayer_filter"&gt;Bayer filter&lt;/a&gt; or similar
technologies, mimic this mechanism to capture colors.&lt;/p&gt;&lt;p&gt;It can be noted that for the same perceived color (fixed
&lt;span&gt;\(\begin{bmatrix} L_0 &amp;amp; M_0 &amp;amp; S_0 \end{bmatrix}\)&lt;/span&gt;), there are infinite
possible spectra &lt;span&gt;\(J(\lambda)\)&lt;/span&gt; that can produce the same perception.
This is called
&lt;a href="https://en.wikipedia.org/wiki/Metamerism_(color)"&gt;metamerism&lt;/a&gt; which
enables modern displays to reproduce or approximate colors with a spectra
different from the real world ones. It is also, in fact, true that modern
displays (such as LCD, OLED, etc.) work by exploiting this method, namely,
they have three kinds of primary color lights red, green and blue that have
artificial but fixed spectra, and the ability to adjust the intensity of
each primary color. Namely, let &lt;span&gt;\(r\)&lt;/span&gt;, &lt;span&gt;\(g\)&lt;/span&gt; and &lt;span&gt;\(b\)&lt;/span&gt; be the
intensities of the RGB lights respectively (which happens to be the RGB
values we usually read in digital images) and let &lt;span&gt;\(J_R(\lambda)\)&lt;/span&gt;,
&lt;span&gt;\(J_G(\lambda)\)&lt;/span&gt; and &lt;span&gt;\(J_B(\lambda)\)&lt;/span&gt; be the fixed, artificial spectra
of the RGB lights, the overall spectrum emitted by the display can be
expressed as:&lt;/p&gt;&lt;div class="equation"&gt;\[J_\text{display}(\lambda) = r J_R(\lambda) + g J_G(\lambda) + b J_B(\lambda)\]&lt;/div&gt;&lt;p&gt;Consider one kind of cone cell, say L, to recreate &lt;span&gt;\(L_0\)&lt;/span&gt;, we have:&lt;/p&gt;&lt;div class="equation"&gt;\begin{align*}
L_0 &amp;amp;= \int_{-\infty}^\infty J_\text{display}(\lambda) s_L(\lambda) d\lambda \\
L_0 &amp;amp;= \int_{-\infty}^\infty \left( r J_R(\lambda) + g J_G(\lambda) + b J_B(\lambda) \right) s_L(\lambda) d\lambda \\
L_0 &amp;amp;= r \int_{-\infty}^\infty J_R(\lambda) s_L(\lambda) d\lambda + g \int_{-\infty}^\infty J_G(\lambda) s_L(\lambda) d\lambda + b \int_{-\infty}^\infty J_B(\lambda) s_L(\lambda) d\lambda
\end{align*}&lt;/div&gt;&lt;p&gt;Notice how &lt;span&gt;\(L_0\)&lt;/span&gt; is a linear combination of
&lt;span&gt;\(\int_{-\infty}^\infty J_R(\lambda) s_L(\lambda) d\lambda\)&lt;/span&gt;,
&lt;span&gt;\(\int_{-\infty}^\infty J_G(\lambda) s_L(\lambda) d\lambda\)&lt;/span&gt; and
&lt;span&gt;\(\int_{-\infty}^\infty J_B(\lambda) s_L(\lambda) d\lambda\)&lt;/span&gt; with
coefficients &lt;span&gt;\(r\)&lt;/span&gt;, &lt;span&gt;\(g\)&lt;/span&gt; and &lt;span&gt;\(b\)&lt;/span&gt;. These integrals are named as
sensitivities of the display primaries to the L cone cell, denoted as
&lt;span&gt;\(S_{L,R}\)&lt;/span&gt;, &lt;span&gt;\(S_{L,G}\)&lt;/span&gt; and &lt;span&gt;\(S_{L,B}\)&lt;/span&gt; respectively so that
&lt;span&gt;\(L = r S_{L,R} + g S_{L,G} + b S_{L,B}\)&lt;/span&gt;. Thus, we can represent the
color perception for L, M and S cone cells caused by RGB light intensities
in matrix form regarding the sensitivities &lt;span&gt;\(\mathbf{S}\)&lt;/span&gt;:&lt;/p&gt;&lt;div class="equation"&gt;\[\begin{bmatrix}
L \\
M \\
S
\end{bmatrix}
=
\begin{bmatrix}
S_{L,R} &amp;amp; S_{L,G} &amp;amp; S_{L,B} \\
S_{M,R} &amp;amp; S_{M,G} &amp;amp; S_{M,B} \\
S_{S,R} &amp;amp; S_{S,G} &amp;amp; S_{S,B}
\end{bmatrix}
\begin{bmatrix}
r \\
g \\
b
\end{bmatrix}\]&lt;/div&gt;&lt;p&gt;so that to recreate color perceptions, we only need to calculate:&lt;/p&gt;&lt;div class="equation"&gt;\[\begin{bmatrix}
r \\
g \\
b
\end{bmatrix}
=
\mathbf{S}^{-1}
\begin{bmatrix}
L \\
M \\
S
\end{bmatrix}\]&lt;/div&gt;&lt;p&gt;which is trivial now. And sometimes, the values of &lt;span&gt;\(r\)&lt;/span&gt;, &lt;span&gt;\(g\)&lt;/span&gt; and
&lt;span&gt;\(b\)&lt;/span&gt; may exceed the display's capability (for example, negative values
or values larger than the maximum intensity), in which case we need to go
creative with color management techniques such as
&lt;a href="https://en.wikipedia.org/wiki/Tone_mapping"&gt;tone mapping&lt;/a&gt; and
&lt;a href="https://en.wikipedia.org/wiki/Gamut_mapping"&gt;gamut mapping&lt;/a&gt; to find
the best visually-pleasing color that the display can produce.&lt;/p&gt;</content></entry><entry><title>《死亡搁浅》玩后感及艺术评论思考</title><link rel="alternate" href="https://ycao.net/posts/ds-review.html"/><id>https://ycao.net/posts/ds-review.html</id><updated>2025-11-15T00:00:00Z</updated><content type="html">&lt;p&gt;今日通关了《死亡搁浅》，不得不提，在游玩时我本以为这是后疫情作品在反思疫情隔离，没想到实际上游戏刚好于19年发售，真是巧合。&lt;/p&gt;&lt;p&gt;《死亡搁浅》最独到的地方，在于实现了作者表达、剧情和游戏性的统一。小岛秀夫的主要创作意图是体现人与人之间互帮互助的重要性，游戏的剧情便是Sam作为“送货人”将后末日世界中支离破碎的美国重新连接。创新点在于，游戏的核心玩法——送货——直接服务于这一主题。它无时无刻不在用“送货的艰辛”与“其他玩家留下的建筑所带来的帮助”形成鲜明对比，点赞系统也让玩家在帮助他人时获得反馈，大多数玩家都有类似的情感经验（举例&lt;sup&gt;&lt;a href="#cite-ds-review-1"&gt;[1]&lt;/a&gt;&lt;/sup&gt;）。&lt;/p&gt;&lt;p&gt;《死亡搁浅》的评价不出意料地出现分歧，主要集中在游戏性不强，玩法单一。&lt;sup&gt;&lt;a href="#cite-ds-review-2"&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;sup&gt;&lt;a href="#cite-ds-review-3"&gt;[3]&lt;/a&gt;&lt;/sup&gt;Sylvester在他的《游戏设计》中提出，游戏是“体验生成引擎”&lt;sup&gt;&lt;a href="#cite-ds-review-4"&gt;[4]&lt;/a&gt;&lt;/sup&gt;，也就是用游戏独有的交互性（机制、系统）生成特定的体验（experience）。《死亡搁浅》的游戏性争议来源于玩家并没有期待它的交互（游戏性）部分被刻意地设计为无聊、困难和恐怖以衬托其主题。但若是以体验传递的角度分析，玩家成功感受到了情感——小岛秀夫所希望玩家体验到的“艰苦”、“孤独”和“互助”——这便是游戏设计成功的体现。传统观点认为游戏必须“好玩”，但好玩也是一种情感体验，具体可以细分为成就感（沙盒、竞技常见）、爽感（精美画面、大场面等（爆米花电影中也频繁使用，是惯常的懒惰套路））等。传统游戏评论期望游戏可以且仅可以在“交互”中带来“好玩”，却忽略了其它的排列组合，也就是动态交互（游戏机制）和传统艺术（过场动画、美术、剧情）可以表达传统艺术的表达，如作者的喜怒哀乐、对社会问题的反思。&lt;/p&gt;&lt;p&gt;此评价框架可以被进一步泛化：游戏与传统文娱的区别在于，传统文娱产品（文学、电影、绘画等）运用叙事和/或视听手段，而游戏则运用交互手段，但他们的目标都为将情感体验传递给受众。评价一段文学、音乐、电影、游戏“好”，所指应为作者成功使读者体验到了他设计过的情感。人与人交流信息会需要证明一些命题，证明本身定然是理性（logos）或者感性（pathos）二选一，晓之以理便是论文，动之以情便是艺术。&lt;/p&gt;&lt;p&gt;2025/12/07追加：本文混淆了“艺术评价”和“艺术家创作评价”，前者不是良定义的/尚存争议。&lt;/p&gt;</content></entry><entry><title>《星际拓荒》玩后感</title><link rel="alternate" href="https://ycao.net/posts/ow-review.html"/><id>https://ycao.net/posts/ow-review.html</id><updated>2025-09-02T00:00:00Z</updated><content type="html">&lt;blockquote&gt;以下内容包含对Outer Wilds剧情的剧透。&lt;/blockquote&gt;&lt;p&gt;Outer Wilds是一部以探索的方式，献给探索者的赞歌。&lt;/p&gt;&lt;p&gt;自伊始以来，人类祖先天然对远处充满好奇。对“那边”的好奇驱使我们走出非洲大陆，在地球上开枝散叶。Outer Wilds直指人类天然的好奇，游戏没有目标，只有开始天文台的些许指引，剩下的道路需要玩家顺着一个个未知现象自己走出。一开始，玩家急于知道面具是什么；然后地面上零零星星的遗迹吸引着玩家的兴趣；而Nomai文物上字里行间透出的零碎信息又将玩家带入更深的地方。“我发现了！”这一古老的欣喜不断驱动着玩家，给予各种不同的经历。&lt;/p&gt;&lt;p&gt;Outer Wilds中，没有传统意义上的成长：玩家不会获得新的道具，取得更高的数值。每一个循环结束，改变的只有玩家掌握的知识。但神奇之处就在于一次次的循环里，这些知识真切地带来了改变。玩家渐渐理解了过去，通晓宇宙的规律。玩家需要的不是一个数值，一个钥匙，只是一段信息。所有或简单或复杂，乃至可以帮助宇宙脱离循环的结构，从第一次在篝火睁开双眼以来，就一直躺在那里，等待着玩家用一系列特定的动作将其发掘。&lt;/p&gt;&lt;p&gt;很多游戏都给谜题套上叙事的外皮，而Outer Wilds则用谜题叙事。游戏的高潮发生于破碎的故事串联在一起，描绘出一部悲壮的史诗。我最为震撼的经历在轨道探测器追踪站：电脑显示出Nomai文明为了绕过超新星只爆发一次（显而易见）的限制，利用时间循环保留数据；通过反复随机地探测9M+次，遍历了整个星系的空间，找到了Eye of the Universe。每次循环的开始，主角睁开眼睛就可以看到探测器被随机地射入深空，其实就是Nomai在几十万年后仍在苦苦寻找答案。在若干关键节点，玩家都可以感受到所谓Aha Moment。因所有线索都由玩家经历万难拼凑出，这些“突然醒悟”带来了无比强烈的冲击力。&lt;/p&gt;&lt;p&gt;游戏结局给这部史诗画上了圆满而富有哲理的句号：我握着高级跃迁核心——Nomai的科技结晶——冒着结束太阳系的风险，重启了废弃的飞船。Final Voyage主旋律与循环结束音乐一样，却多了额外的频率，讲述着Nomai持续几十万年却被意外停止的狂奔，最后一块拼图被玩家合上，也暗示了“这次有所不同”。Eye幻化出博物馆和树林，在其中旧宇宙走向热寂；Nomai骸骨前一刻还在望向星空，一眨眼的功夫飞船就向未知奔去；量子态的探索者们围在一起，用最后一曲勾勒出新的宇宙。一代代探索者的坚持与牺牲才托举着现代文明在巨人肩膀上到达了如此的高度。&lt;/p&gt;&lt;p&gt;Outer Wilds有且只有探索这一个玩法，但这就够了，因探索是人类最古老最宝贵的本能。&lt;/p&gt;</content></entry><entry><title>Review on ASUS Zenbook S14 Laptop (LNL) with Linux</title><link rel="alternate" href="https://ycao.net/posts/asus-ux5406-review.html"/><id>https://ycao.net/posts/asus-ux5406-review.html</id><updated>2025-05-02T00:00:00Z</updated><content type="html">&lt;p&gt;I am trying to pick a laptop that is as close to Macbook Mx Airs as
possible - light, low power consumption and long battery life, with the
only exception of not being an Apple
device&lt;sup&gt;&lt;a href="#cite-asus-ux5406-1"&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;sup&gt;&lt;a href="#cite-asus-ux5406-2"&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;sup&gt;&lt;a href="#cite-asus-ux5406-3"&gt;[3]&lt;/a&gt;&lt;/sup&gt; -
for the upcoming coursework. As for the chip itself, the closest I can get
is Intel's
&lt;a href="https://www.pcworld.com/article/2463714/tested-intels-lunar-lake-wants-you-to-forget-snapdragon-ever-existed.html"&gt;Lunar Lake CPUs&lt;/a&gt;
whose successor unfortunatelly Intel will not develop due to their
&lt;a href="https://medium.com/@mingchikuo/inside-intels-lunar-lake-a-promise-that-became-a-problem-e91d872cee62"&gt;management incompetentness&lt;/a&gt;.
Among all LNL laptops, it seems that
&lt;a href="https://www.notebookcheck.net/Asus-Zenbook-S-14-UX5406-laptop-review-Excellent-everyday-laptop-with-Intel-Lunar-Lake.892978.0.html"&gt;ASUS Zenbook S14&lt;/a&gt;
is of top quality, has a reasonable price and does not repulse Linux like
Lenovo's counterpart Yoga&lt;sup&gt;&lt;a href="#cite-asus-ux5406-4"&gt;[4]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&lt;p&gt;According to &lt;a href="https://github.com/dantmnf/zenbook-s14-linux"&gt;a GitHub repo&lt;/a&gt;
(more on this repo later), LNL is well-supported on Linux 6.12.5+, which is
not a problem on my OpenSUSE TW. It mostly works out of the box, has nice
secure boot and TPM2.0 support, with the need to install &lt;code&gt;sof-firmware&lt;/code&gt;
for the audio to work. Surprisingly, the NPU card has its driver
&lt;code&gt;intel_vpu&lt;/code&gt; loaded. I've yet to test it, which Intel promotes as having
&lt;a href="https://www.intel.com/content/www/us/en/products/sku/240957/intel-core-ultra-7-processor-258v-12m-cache-up-to-4-80-ghz/specifications.html"&gt;47 TOPS&lt;/a&gt;;
I was really tempted to try out the NPU in the pre-installed Windows but
gave up when copilot forced me to login my Microsoft Account.&lt;/p&gt;&lt;p&gt;The battery life is as good as it promoted. As the writing of this post,
which happens to be my expected workflow with this laptop, I am working on
my Emacs with some trivial packages and the built-in Mozilla Firefox with
about 20 pages loaded (no heavy media); in the background there is a
syncthing daemon running, a Mozilla Thunderbird, a Libreoffice Writer and an
Akregator; my desktop is KDE and has no special customization. Under a
battery of 70%, screen brightness 20% and power profile set to
&lt;code&gt;powersave&lt;/code&gt;, the estimated battery life reaches 8 hours. Expect a
charger-free day when your job only involves light office-work or light
development. The laptop is also quite pleasantly chilly, whose CPU sits
under 40C in the room temperature of 25C. One can barely feel any heat on
the chasis.&lt;/p&gt;&lt;p&gt;It does have some quirks, though, with the first being KDE seemingly not
recognizing my graphics card. It kept telling me it's using &lt;code&gt;llvmpipe&lt;/code&gt;
while actually utilizing the GPU. The information is fixed by installing
&lt;code&gt;intel-vaapi-driver&lt;/code&gt;. The second counter-intuitive point is that
&lt;code&gt;intel_gpu_top&lt;/code&gt; doesn't work on such &lt;code&gt;xe&lt;/code&gt; GPUs. It's an easy fix
as &lt;code&gt;nvtop&lt;/code&gt; is a nice replacement to it.&lt;/p&gt;&lt;p&gt;The last issue was not trivial and took me three days to find the cause.
The laptop occasionally slowed down until I reboot and
&lt;code&gt;cpupower frequency-info&lt;/code&gt; showed me that CPU frequency policy was
randomly throttled to only 400MHz max and changing the governer had zero
help. I suspected on &lt;code&gt;BD_PROCHOT&lt;/code&gt;, even a faulty sensor in my laptop,
but &lt;code&gt;rdmsr&lt;/code&gt; showed no signs of error. I asked for help on OpenSUSE
forum, Tom's Hardware forum and even Reddit. The issue is eventually
clarified
&lt;a href="https://github.com/dantmnf/zenbook-s14-linux/issues/11#issuecomment-2846600130"&gt;by a dude met in the previously mentioned repo&lt;/a&gt;.
It seems that &lt;code&gt;powertop&lt;/code&gt; has some iffy interaction with the firmware
and randomly throttles max frequency even if I only use the monitor part of
it. The issue has never occured after stopping using &lt;code&gt;powertop&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;That's all for now, and I'm overall pretty satisfied with this machine and
consider it a pretty close approximation of the Macbook M1 Air.&lt;/p&gt;</content></entry><entry><title>LLM will NEVER be AGI: The Proof</title><link rel="alternate" href="https://ycao.net/posts/llm-agi.html"/><id>https://ycao.net/posts/llm-agi.html</id><updated>2024-08-20T00:00:00Z</updated><content type="html">&lt;p&gt;The proof is trivial with a little help of a necessary condition of
complexity theories. All LLM runs under the complexity of &lt;span&gt;\(O(n)\)&lt;/span&gt;,
where &lt;span&gt;\(n\)&lt;/span&gt; is the length of the output.&lt;/p&gt;&lt;p&gt;Suppose LLM is AGI, then it is able to solve any problem that a human can
solve. Consider the following problem:&lt;/p&gt;&lt;blockquote&gt;Given a string &lt;span&gt;\(s\)&lt;/span&gt; of length &lt;span&gt;\(n\)&lt;/span&gt;, determine whether &lt;span&gt;\(s\)&lt;/span&gt; is a
palindrome. Answer "Y" if it is and "N" if it isn't.&lt;/blockquote&gt;&lt;p&gt;Apprently, a human can solve this problem, and it is easy to prove that the
problem must be solved in at least &lt;span&gt;\(O(n)\)&lt;/span&gt; time.&lt;/p&gt;&lt;p&gt;Since the output of this problem is of a constant length, LLM must solve
this problem in &lt;span&gt;\(O(1)\)&lt;/span&gt; time, which is a contradiction. Thus LLM cannot
solve a problem that a human can solve. Therefore LLM is not AGI.
&lt;span&gt;\(\blacksquare\)&lt;/span&gt;&lt;/p&gt;&lt;p&gt;EDIT: Chain of Thoughts breaks the proof, allowing LLM to solve the problem
in arbitrary time.&lt;/p&gt;</content></entry><entry><title>On Programming Languages</title><link rel="alternate" href="https://ycao.net/posts/on-programming-languages.html"/><id>https://ycao.net/posts/on-programming-languages.html</id><updated>2024-05-01T00:00:00Z</updated><content type="html">&lt;p&gt;While it is true that most of the time while developing software, we just
pick either C++, Java, Python, etc. and start coding simply because A these
languages are already well-established and have a lot of libraries and B we
are already familiar with them. Yet new languages still emerge from time to
time, such as Rust, TypeScript, and Julia, which are happily adopted (and
hated) by developers. But few have thought about &lt;i&gt;what are we actually
creating&lt;/i&gt;.&lt;/p&gt;&lt;h2 id="by-computation"&gt;By computation, you mean...&lt;/h2&gt;&lt;p&gt;Computers, by definition, compute. And we utilize programming languages to
instruct the computer to compute. However, we actually have no idea what
computing means. You may simply argue against this stating that "Well I
know Turing Machine!" Indeed,
&lt;a href="https://en.wikipedia.org/wiki/Turing_machine"&gt;Turing machine&lt;/a&gt; is a
great computational model. Along with it also comes the
&lt;a href="https://en.wikipedia.org/wiki/Lambda_calculus"&gt;&lt;span&gt;\(\lambda\)&lt;/span&gt;-calculus&lt;/a&gt;
(also my &lt;a href="/posts/an-introduction-to-lambda-calculus.html"&gt;blog post&lt;/a&gt;),
&lt;a href="https://en.wikipedia.org/wiki/General_recursive_function"&gt;&lt;span&gt;\(\mu\)&lt;/span&gt;-recursive functions&lt;/a&gt;,
etc. Surprisingly, these intuitively vastly different models are actually
equivalent in terms of computability, which is known as
&lt;a href="https://en.wikipedia.org/wiki/Turing_completeness"&gt;Turing equivalent&lt;/a&gt;.
We also have created problems that are
&lt;a href="https://en.wikipedia.org/wiki/Undecidable_problem"&gt;undecidable&lt;/a&gt;
&lt;a href="https://math.stackexchange.com/a/2268351/738593"&gt;and&lt;/a&gt; uncomputable.
But what makes Turing machine / &lt;span&gt;\(\lambda\)&lt;/span&gt;-calculus special? Why do
these (fundamentally identical) computational models decide what is
computable? Back to the question, what is computation? It turns out that
&lt;b&gt;we have no idea&lt;/b&gt;. The
&lt;a href="https://en.wikipedia.org/wiki/Church%E2%80%93Turing_thesis"&gt;Turing-Church Conjecture&lt;/a&gt;
states that these computational models are identical because they all
capture the essence of computation. But what is the essence of computation?
It's never formally defined. (You can't define it by stating that Turing
machine means computation, after all the concept "&lt;i&gt;essence of
computation&lt;/i&gt;" is there because we have so many coincidentally equivalent
models and that may imply some deeper meaning of being computable.) Maybe
there exist some other models that have different computational power (in
terms of computationability) that we have never thought of. Maybe some
problems are computable but not by Turing machine. We simply don't know.&lt;/p&gt;&lt;h2 id="why-care"&gt;That's enough metaphysics nonsense. Why would I care?&lt;/h2&gt;&lt;p&gt;The fact is while these models are the same in terms of Math, they still
differ in terms of mind and what's more, performance. Functional guys
trying to lure you into their nasty world of &lt;span&gt;\(\lambda\)&lt;/span&gt;-calculus because
most of the time functional stuff is more expressive and concise. But you
may fight back saying their code going Stack Overflow because of using lazy
evaluation wrong is hilarious and absurd. Modern languages no longer base
themselves on Turing machine or &lt;span&gt;\(\lambda\)&lt;/span&gt;-calculus but
&lt;a href="https://en.wikipedia.org/wiki/Random-access_machine"&gt;RAM-access machines&lt;/a&gt;
simply because the model approximates real-world computers. (It would be
great if &lt;a href="https://en.wikipedia.org/wiki/Lisp_machine"&gt;LISP machines&lt;/a&gt;
still exist.) While it holds that a Turing machine emulates a RAM-access
machine, it does so in
&lt;a href="https://cs.stackexchange.com/a/22419/160863"&gt;polynomial time&lt;/a&gt;, which
is stopping you from
&lt;a href="https://en.wikipedia.org/wiki/Brainfuck"&gt;coding like this&lt;/a&gt;.&lt;/p&gt;&lt;h2 id="jmp-load-store"&gt;But I don't code using &lt;code&gt;JMP&lt;/code&gt; and &lt;code&gt;LOAD&lt;/code&gt;/&lt;code&gt;STORE&lt;/code&gt; either!&lt;/h2&gt;&lt;p&gt;Indeed. Our poor little brains (except
&lt;a href="https://en.wikipedia.org/wiki/Lists_of_mathematicians"&gt;theirs&lt;/a&gt;) have
already been proven to not have the ability to code in assembly, and
Haskell isn't just about &lt;span&gt;\(\lambda\)&lt;/span&gt;-calculus. Abstraction comes into
place to free our tiny RAM. By abstraction, I would like to elaborate on it
as "working on partial information". For example, I know that whichever
input &lt;span&gt;\(f\)&lt;/span&gt; always gives the same output if the input is the same simply
because &lt;span&gt;\(f\)&lt;/span&gt; is a function. In the C language, we define functions to
hide away actual procedures working solely on the underlying meaning. Good
languages free our brains and less-en the information we are working on.
It's abstraction all the way down.&lt;/p&gt;&lt;h2 id="creating-languages"&gt;And by creating languages you mean...&lt;/h2&gt;&lt;p&gt;Abstractions are great, but (in terms of software engineering) when it
comes to abstraction there are no underlying metaphysics implications (thank
god) nor formal definitions. It's about doing whatever the cuss a developer
would like to. In Golang you have &lt;code&gt;interface&lt;/code&gt;s, in C you have
functions, in C++ you have &lt;code&gt;class&lt;/code&gt;es, in Haskell, you have functions
all over the place. For
&lt;a href="https://en.wikipedia.org/wiki/Expression_problem"&gt;the expression problem&lt;/a&gt;,
some choose to dispatch methods vertically while some do it horizontally.
Types are not primitives but abstractions too. Everything is just bits and
bytes, interpreting an IEEE double as a short won't cause any fundamental
troubles, and sometimes we do it intentionally. Types present because most
of the time we want to keep it consistent. All of these show that
abstraction is largely ruled by relativism. That's where LISP comes into
place. It, again I'd like to elaborate as, abstracts abstraction by using
macros. Consider the following program:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-scheme"&gt;(define-syntax unless
  (syntax-rules ()
    ((_ condition body ...)
     (if (not condition) (begin body ...)))))

(unless (= x 0)
  (display "x is not zero")
  (newline))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It defines a macro that expands to&lt;/p&gt;&lt;pre&gt;&lt;code class="language-scheme"&gt;(if (not (= x 0))
    (begin
      (display "x is not zero")
      (newline)))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;at compile time. One thing that is truly great about LISP macros is that
you can do arbitrary computation at compile time. For example, for C++ guys
who love classes, there exists CLOS (Common Lisp Object System) that is
written in LISP itself, without going into your
indeed-turing-complete-but-all-cluttered-together-only-god-can-understand-CPP-templating-nonsense.&lt;/p&gt;&lt;h2 id="tricks"&gt;Don't you play tricks on me&lt;/h2&gt;&lt;p&gt;Indeed, a compiler, by its definition, does calculations at compile time.
What LISP provides can be seen as a well-designed, modular compiler
framework. It blurs the line between a language and the tech behind a
language. I think we can happily conclude that by creating languages, we are
creating new ways of abstracting data and procedures that fit our needs. PLs
will just keep evolving. It's not a proven fact, it's some kind of art
created by humans.&lt;/p&gt;&lt;figure&gt;&lt;img src="/files/20240501/whackygpt.png" alt="GPT fond of LISP"/&gt;&lt;figcaption&gt;GPT fond of LISP&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;And it also turns out that even GPT is fond of LISP, so you'd better check
it out.&lt;/p&gt;</content></entry><entry><title>Simulating Gravitational Lensing</title><link rel="alternate" href="https://ycao.net/posts/simulating-gravity-lensing.html"/><id>https://ycao.net/posts/simulating-gravity-lensing.html</id><updated>2023-09-03T00:00:00Z</updated><content type="html">&lt;p&gt;Ever wondered why the
&lt;a href="https://www.imdb.com/title/tt0816692/mediaviewer/rm4154473472?ref_=ext_shr_lnk"&gt;black hole in &lt;i&gt;Interstellar&lt;/i&gt;&lt;/a&gt;
looks like two rings intersecting each other orthogonally? It's because of
the
&lt;a href="https://hubblesite.org/contents/articles/gravitational-lensing"&gt;gravitational lensing effect&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Physics behind this phenomenon comes from Einstein's general relativity and
is quite complicated. (If you are interested,
&lt;a href="https://qr.ae/pyE9TW"&gt;this Quora post&lt;/a&gt; has an in-depth walk-through.)
However, There is an important clue that light travels in the same way as a
non-zero mass particle does. This means that we can simulate the
gravitational lensing effect by simulating the trajectory of a particle in
a gravitational field since we don't have to worry about the speed of
light, etc. in this simplified model.&lt;/p&gt;&lt;p&gt;The model starts like ray tracing. We start with a screen and a virtual
camera. The screen is divided into a grid of pixels. For each pixel, we
shoot a virtual particle from the camera to the pixel. The position of the
camera becomes the initial position of the particle. The velocity of the
particle is set to be the unit vector pointing from the camera to the
pixel.&lt;/p&gt;&lt;p&gt;The particle is then calculated regarding the gravitational field, that is:&lt;/p&gt;&lt;div class="equation" id="eq-gravity"&gt;\[\vec{F} = m\vec{a} = \sum_{M, \vec{d} \in \text{mass-points}} \frac{GMm}{\vec{d}^2}\hat{d}\]&lt;span class="eq-number"&gt;(1)&lt;/span&gt;&lt;/div&gt;&lt;p&gt;Where &lt;span&gt;\(d\)&lt;/span&gt; is the distance between the particle and the mass point,
&lt;span&gt;\(M\)&lt;/span&gt; is the mass of the mass point, &lt;span&gt;\(m\)&lt;/span&gt; is the virtual mass of the
particle, &lt;span&gt;\(G\)&lt;/span&gt; is the gravitational constant, and &lt;span&gt;\(\hat{d}\)&lt;/span&gt; is the
unit vector pointing from the mass point to the particle.&lt;/p&gt;&lt;p&gt;Since &lt;span&gt;\(m\)&lt;/span&gt; cancels out (the mass of an object doesn't affect its
acceleration in a gravitational field), we can simplify &lt;a href="#eq-gravity"&gt;(1)&lt;/a&gt; to:&lt;/p&gt;&lt;div class="equation"&gt;\[\vec{a} = \frac{\mathrm{d}^2 \vec{x}}{\mathrm{d}t^2} = \sum_{M, \vec{d} \in \text{mass-points}} \frac{GM}{\vec{d}^2}\hat{d}\]&lt;/div&gt;&lt;p&gt;An analytical solution might exist but it is too complicated to be useful
when it comes to calculate the collision of the trajectory and the visible
objects.&lt;/p&gt;&lt;p&gt;Instead, we can use a numerical method to solve this equation. The movement
of the particle can be calculated in a discrete manner. For each time step
&lt;span&gt;\(\Delta t\)&lt;/span&gt;, the model checks if the segment
&lt;span&gt;\(x(t) \rightarrow x(t+\Delta t)\)&lt;/span&gt; bumps into any visible object. If so,
the model stops the particle at the point of collision and paints the pixel
with the color of the object.&lt;/p&gt;&lt;p&gt;Although the method seems dumb, it actually completes calculation in
reasonable time. You can find my implementation
&lt;a href="https://github.com/yikerman/glens"&gt;glens at GitHub&lt;/a&gt;. The software is
developed in Rust and outputs images in PPM format.&lt;/p&gt;&lt;p&gt;With a bit of scripting (or meta-scripting?) even videos can be generated.&lt;/p&gt;&lt;video width="480" height="270" controls&gt;&lt;source src="/files/20230903/pass.mp4" type="video/mp4"&gt;&lt;/video&gt;&lt;p&gt;In the video above, an invisible black hole passes two visible stars. The
gravitational lensing effect is clearly visible. Stars behind the black
hole are distorted and duplicated. For example, in this image:&lt;/p&gt;&lt;figure&gt;&lt;img src="/files/20230903/pass.png" alt="Blackhole passes stars"/&gt;&lt;figcaption&gt;Blackhole passes stars&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;The yellow star is actually behind the black hole. However, due to the
gravitational lensing effect, the light is redirected into a ring (known as
&lt;a href="https://en.wikipedia.org/wiki/Einstein_ring"&gt;the Einstein ring&lt;/a&gt;)
around the black hole. The left blue star is a duplicated version of the
original one. Light is bent by the black hole and redirected to the actual
position of the star, passing the back of the black hole.&lt;/p&gt;&lt;p&gt;The video below shows two black holes with
&lt;a href="https://en.wikipedia.org/wiki/Accretion_disk"&gt;accretion disk&lt;/a&gt;
dancing around each other. The gravitational lensing effect is even more
obvious.&lt;/p&gt;&lt;video width="480" height="270" controls&gt;&lt;source src="/files/20230903/bh.mp4" type="video/mp4"&gt;&lt;/video&gt;</content></entry><entry><title>Visiting NASA Goddard Space Flight Center</title><link rel="alternate" href="https://ycao.net/posts/visit-goddard.html"/><id>https://ycao.net/posts/visit-goddard.html</id><updated>2023-08-01T00:00:00Z</updated><content type="html">&lt;p&gt;I have visited NASA Goddard Space Flight Center on August 1st, 2023. It's
a great experience to see the real spacecrafts and the people behind them.
What's more exciting is that photos are allowed in the visitor center, so I
can share some of them here.&lt;/p&gt;&lt;figure&gt;&lt;img src="/files/20230801/dustless.jpg" alt="Dustless Clean Room"/&gt;&lt;figcaption&gt;Dustless Clean Room&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;This is the dustless clean room where the spacecrafts are assembled. The
air is filtered to remove dust and other particles. The temperature and
humidity are also controlled to prevent corrosion and other problems.&lt;/p&gt;&lt;p&gt;The big frame structure in the left upper corner is a model for engineers
to see if parts fit the final spacecraft.&lt;/p&gt;&lt;figure&gt;&lt;img src="/files/20230801/indicator.jpg" alt="Indicator Light"/&gt;&lt;figcaption&gt;Indicator Light&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src="/files/20230801/assembly.jpg" alt="Engineers Assembling"/&gt;&lt;figcaption&gt;Engineers Assembling&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Another important part of the space center is the testing facilities.&lt;/p&gt;&lt;p&gt;The spacecrafts are tested in a vacuum chamber to simulate the space
environment. The chamber can be cooled or heated to extreme temperatures.
It can also simulate radiation and other space hazards.&lt;/p&gt;&lt;figure&gt;&lt;img src="/files/20230801/can.jpg" alt="Testing Can"/&gt;&lt;figcaption&gt;Testing Can&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src="/files/20230801/chamber.jpg" alt="Testing Chamber"/&gt;&lt;figcaption&gt;Testing Chamber&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;The spacecrafts are also tested in a vibration table to simulate the
launch as shown in the mirror.&lt;/p&gt;&lt;figure&gt;&lt;img src="/files/20230801/shake.jpg" alt="Vibration Table"/&gt;&lt;figcaption&gt;Vibration Table&lt;/figcaption&gt;&lt;/figure&gt;</content></entry><entry><title>My 2023 Swift Student Challenge</title><link rel="alternate" href="https://ycao.net/posts/ssc-2023-experience.html"/><id>https://ycao.net/posts/ssc-2023-experience.html</id><updated>2023-05-17T00:00:00Z</updated><content type="html">&lt;p&gt;Ah, Swift Student Challenge, the once in a year competition from our
dearest Apple.&lt;/p&gt;&lt;p&gt;From the very first moment I have decided to make some educational apps
since I have seen an accepted submission which teaches the user about
asymmetric cryptography. In fact, I had several inspirations on my list:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Combine AR technology to show a 4D object projected onto a 3D space.&lt;/li&gt;&lt;li&gt;Introduce signal processing from why some people sing better than others.&lt;/li&gt;&lt;li&gt;Introduce the Iterated Function System to show that Math can be fun and beautiful.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;And at last I chose the third one since it's the most approachable one and
time is quite limited to me. It's also because I have made a small app
&lt;a href="https://github.com/yikerman/QIFS"&gt;QIFS&lt;/a&gt; about this concept.
(However the code base is messy and unmaintained.)&lt;/p&gt;&lt;h2 id="ifs"&gt;IFS&lt;/h2&gt;&lt;p&gt;In short, IFS is a set of functions
&lt;span&gt;\(\lbrace f_i: X \mapsto X \mid i \in [1,n] \rbrace, n \subset \mathbb{N}\)&lt;/span&gt;
under a metric space &lt;span&gt;\(X\)&lt;/span&gt;. The image is get by repeating the following
process infinite times:&lt;/p&gt;&lt;div class="equation"&gt;\[S \gets \bigcup_{i=1}^{n} \bigcup_{s \in S} f_i(s)\]&lt;/div&gt;&lt;p&gt;In most cases, we want to keep things simple, so we choose
&lt;span&gt;\(X = \mathbb{R}^2\)&lt;/span&gt; and &lt;span&gt;\(f_i, \forall i\)&lt;/span&gt; to be affine
transformations. The most famous example is the Sierpinski triangle, which
is generated by the following three functions:&lt;/p&gt;&lt;div class="equation"&gt;\begin{align*}
&amp;amp; f_1(x,y) = (\frac{x}{2}, \frac{y}{2}) \\
&amp;amp; f_2(x,y) = (\frac{x}{2} + \frac{1}{2}, \frac{y}{2}) \\
&amp;amp; f_3(x,y) = (\frac{x}{2} + \frac{1}{4}, \frac{y}{2} + \frac{\sqrt{3}}{4})
\end{align*}&lt;/div&gt;&lt;p&gt;More examples can be found
&lt;a href="http://larryriddle.agnesscott.org/ifs/ifs.htm"&gt;here&lt;/a&gt;. Thanks to the
&lt;a href="https://math.stackexchange.com/questions/1896127/why-does-the-chaos-game-generate-a-fractal"&gt;chaos game algorithm&lt;/a&gt;,
the IFS can be easily plotted by keep choosing a random function from the
set and apply it to the current point. The barebone of the app is thus
finished...&lt;/p&gt;&lt;pre&gt;&lt;code class="language-swift"&gt;class IFSSystem {
    var position: CGPoint = CGPoint(x: 0.5, y: 0.5) // Some random point
    var transforms: [CGAffineTransform]

    init(_ t: [CGAffineTransform]) {
        transforms = t
    }

    func chaosGameStep() -&amp;gt; CGPoint {
        let selected = Int.random(in: 0..&amp;lt;transforms.count)
        position = position.applying(transforms[selected])
        return position
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;... in actually 3 lines of code (Thanks to CoreGraphics or I'll have to
add 10 more lines). Now the only thing left is to make it interactive and
intuitive. The key part is to visualize the transforms &lt;span&gt;\(f\)&lt;/span&gt;. Since we
know it's an affine transformation, which can be decomposed into a linear
transformation and a translation. We can represent it using a
parallelogram where one point represents the translation and the other two
points represent the linear transformation. That is, for transformation:&lt;/p&gt;&lt;div class="equation"&gt;\[f(x,y) = \begin{bmatrix} a &amp;amp; b \\ c &amp;amp; d \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} + \begin{bmatrix} e \\ f \end{bmatrix}\]&lt;/div&gt;&lt;p&gt;The four points are &lt;span&gt;\(A(e,f)\)&lt;/span&gt;, &lt;span&gt;\(B(a+e,c+f)\)&lt;/span&gt;, &lt;span&gt;\(C(b+e,d+f)\)&lt;/span&gt; and
&lt;span&gt;\(D(a+b+e,c+d+f)\)&lt;/span&gt;. In fact, &lt;span&gt;\(\overrightarrow{AB}\)&lt;/span&gt; and
&lt;span&gt;\(\overrightarrow{AC}\)&lt;/span&gt; represents the two transformed &lt;span&gt;\(\hat{i}\)&lt;/span&gt; and
&lt;span&gt;\(\hat{j}\)&lt;/span&gt;.&lt;/p&gt;&lt;p&gt;Having these in mind, we can now visualize the IFS system easily.&lt;/p&gt;&lt;pre&gt;&lt;code class="language-swift"&gt;func iterate(_ transforms: [CGAffineTransform]) {
    var newTs: [CGAffineTransform] = []
    for t in transforms {
        for t2 in transforms {
            newTs.append(
                t2.concatenating(t)
            )
        }
    }
    return newTs
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And the only thing left is to implement the UI now.&lt;/p&gt;&lt;p&gt;For the construction part I made the parallellogram draggable. Although
users can't control the coordinates precisely (which is not necessary),
they can still get a sense of how the transformation works.&lt;/p&gt;&lt;figure&gt;&lt;img src="/files/20230517/cons.png" alt="Construction"/&gt;&lt;figcaption&gt;Construction&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;For the visualization part, I made an "Iterate" buttom which will call the
&lt;code&gt;iterate&lt;/code&gt; function above and plot the new parallelograms.&lt;/p&gt;&lt;figure&gt;&lt;img src="/files/20230517/vis0.png" alt="Visualization 0"/&gt;&lt;figcaption&gt;Visualization 0&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src="/files/20230517/vis1.png" alt="Visualization 1"/&gt;&lt;figcaption&gt;Visualization 1&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src="/files/20230517/vis2.png" alt="Visualization 2"/&gt;&lt;figcaption&gt;Visualization 2&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;And it can goes on and on until the device runs out of memory.&lt;/p&gt;&lt;p&gt;In the end for the rendering part, I utilized the &lt;code&gt;chaosGameStep&lt;/code&gt;
function above and plot the points on the screen. To make the image look
better I added random colors to each transform function.&lt;/p&gt;&lt;figure&gt;&lt;img src="/files/20230517/ren.png" alt="Render"/&gt;&lt;figcaption&gt;Render&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;The final image can also be zoomed in so that users can see the
self-similarity of the fractal.&lt;/p&gt;&lt;figure&gt;&lt;img src="/files/20230517/ren-zoom.png" alt="Zoom in"/&gt;&lt;figcaption&gt;Zoom in&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;And that's it! The app is finished. You can find the source code
&lt;a href="https://github.com/yikerman/IFS"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;h2 id="results"&gt;Results&lt;/h2&gt;&lt;p&gt;I submitted the app on the last day of the submission period. It was a bit
rushed but I think it's still acceptable. And I got the result on the 9th
of May. Unfortunately, I didn't get accepted. I think the main reason is
that the app is not very "useful". In the cryptography example,
asymmetrical cryptography is used in everyday life and is indeed quite
important. However, IFS is just a mathematical concept and is not very
useful in real life. Despite the result, I still think it's a good
experience and I have learned a lot from it. I will definitely try again
next year.&lt;/p&gt;</content></entry><entry><title>Kerbalhopper: PID explained with KSP</title><link rel="alternate" href="https://ycao.net/posts/kerbalhopper.html"/><id>https://ycao.net/posts/kerbalhopper.html</id><updated>2023-04-30T00:00:00Z</updated><content type="html">&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;&lt;p&gt;The Starship test flight by SpaceX is incredibly thrilling! True to your
old habits, you've determined to recreate it in Kerbal Space Program.
Perhaps it would be wise to begin by constructing a Starhopper replica.
After all, you just need to take off and maintain your altitude for a few
seconds, right?&lt;/p&gt;&lt;p&gt;The rocket design is straightforward. All you need to do is affix a Dart
engine beneath a Rockomax Fuel Tank, integrate landing legs, a probe core
and battery together, and you're ready to launch.&lt;/p&gt;&lt;figure&gt;&lt;img src="/files/20230430/craft.png" alt="KerbalHopper Craft"/&gt;&lt;figcaption&gt;KerbalHopper Craft&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;After fiddling aroud with
&lt;a href="/files/20230430/KerbalHopper.craft"&gt;this craft&lt;/a&gt;, you decide
to automate this go-to-an-altitude-and-hold-it process. Being a
programmer, automation comes naturally to you.&lt;/p&gt;&lt;p&gt;Thanks to the community, a mod called
&lt;a href="https://github.com/krpc/krpc"&gt;krpc&lt;/a&gt; exposes the underlying API of
KSP. You can use it to write a script that controls your craft.&lt;/p&gt;&lt;h2 id="the-math-way"&gt;The math way&lt;/h2&gt;&lt;p&gt;You want to figure this problem out by turing it into an optimization
problem. In short, you need to figure out a function
&lt;span&gt;\(f: \mathbb{R} \mapsto [0,1], \text{time} \mapsto \text{throttle}\)&lt;/span&gt;.
According to Newton's law:&lt;/p&gt;&lt;div class="equation"&gt;\[F = ma\]&lt;/div&gt;&lt;p&gt;while the corresponding &lt;span&gt;\(F\)&lt;/span&gt;, &lt;span&gt;\(m\)&lt;/span&gt; and &lt;span&gt;\(a\)&lt;/span&gt; are:&lt;/p&gt;&lt;div class="equation"&gt;\begin{align*}
&amp;amp; F = f(t) + mg + \text{drag}(\frac{\mathrm{d}x}{\mathrm{d}t}) \\
&amp;amp; m = \text{wet mass} - \text{fuel consumption}(\int_{0}^{t} f(u) \mathrm{d}u) \\
&amp;amp; a = \frac{\mathrm{d}^2 x}{\mathrm{d}t^2}
\end{align*}&lt;/div&gt;&lt;p&gt;And you want at some time &lt;span&gt;\(t_0\)&lt;/span&gt;:&lt;/p&gt;&lt;div class="equation"&gt;\begin{align*}
&amp;amp; x = h \\
&amp;amp; \frac{\mathrm{d}x}{\mathrm{d}t} = 0
\end{align*}&lt;/div&gt;&lt;p&gt;Ok it seems that things are getting out of hand. Too many variables are
affecting each other. You wonder if there is a better way to do this. And
it turns out that this is an engineering question: &lt;b&gt;You don't have to
find an optimal solution, you just need to find a good enough one.&lt;/b&gt;&lt;/p&gt;&lt;h2 id="the-engineering-way"&gt;The engineering way&lt;/h2&gt;&lt;p&gt;Time to be creative!&lt;/p&gt;&lt;p&gt;&lt;b&gt;P&lt;/b&gt;&lt;/p&gt;&lt;p&gt;A pretty straightforward way is to let your throttle be proportional to
how far you still need to fly before reaching height &lt;span&gt;\(h\)&lt;/span&gt;.&lt;/p&gt;&lt;p&gt;Let &lt;span&gt;\(E = h - x\)&lt;/span&gt;, then your &lt;span&gt;\(f\)&lt;/span&gt; can be written into:&lt;/p&gt;&lt;div class="equation"&gt;\[f = KE\]&lt;/div&gt;&lt;p&gt;And you implement this into Python:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-python"&gt;import krpc, time

HEIGHT = 200
K = 0.01 # Some constant?
DELTA_T = 0.01


class Controller:
    def __init__(
        self,
        target: float,
        k: float
    ) -&amp;gt; None:
        self.target = target
        self.k = k

    def step(self, current: float, dt: float) -&amp;gt; float:
        # f = KE
        error = self.target - current
        return error * self.k


def main():
    conn = krpc.connect(name="KerbalHopper Controller")
    vessel = conn.space_center.active_vessel
    controller = Controller(
        target=HEIGHT,
        k=K,
    )

    input("Press enter to launch.")
    vessel.control.activate_next_stage()

    while True:
        flight = vessel.flight()
        alt = flight.surface_altitude
        throttle = controller.step(
            current=alt,
            dt=DELTA_T
        )
        vessel.control.throttle = throttle
        print(
            "Altitude: {alt} Throttle: {throttle}".format(
                alt=alt,
                throttle=throttle,
            )
        )
        time.sleep(DELTA_T)

if __name__ == "__main__":
    main()&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After a few trials, you find that the craft isn't going anywhere no matter
what &lt;span&gt;\(K\)&lt;/span&gt; is. Ship keeps oscillating dramatically between &lt;span&gt;\(30\)&lt;/span&gt; and
&lt;span&gt;\(230\)&lt;/span&gt;. This method need some workarounds.&lt;/p&gt;&lt;p&gt;&lt;b&gt;D&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Recall what you do when you're trying to maintain the altitude: When
you're approaching the height, you try not to let the ship fly too fast.&lt;/p&gt;&lt;p&gt;How do you describe &lt;i&gt;not going too fast while approaching&lt;/i&gt;? Yes that's
&lt;span&gt;\(-\frac{\mathrm{d}x}{\mathrm{d}t} = \frac{\mathrm{d}E}{\mathrm{d}t}\)&lt;/span&gt;.
Again, you take the value, snap a content &lt;span&gt;\(K_d\)&lt;/span&gt; onto it and hope this
works.&lt;/p&gt;&lt;div class="equation"&gt;\[f = K_p E + K_d \frac{\mathrm{d}E}{\mathrm{d}t}\]&lt;/div&gt;&lt;p&gt;And you implement this in Python:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-python"&gt;import krpc, time

HEIGHT = 200
KP = 0.02
KD = 0.008 # Some more constant!
DELTA_T = 0.01


class Controller:
    def __init__(self, target: float, kp: float, kd: float) -&amp;gt; None:
        self.target = target
        self.kp = kp
        self.kd = kd
        self.last_error = 0

    def step(self, current: float, dt: float) -&amp;gt; float:
        error = self.target - current

        p = error * self.kp

        # discrete version of dE
        dE = (error - self.last_error) / dt
        d = self.kd * dE

        self.last_error = error
        return p + d


def main():
    conn = krpc.connect(name="KerbalHopper Controller")
    vessel = conn.space_center.active_vessel
    controller = Controller(
        target=HEIGHT,
        kp=KP,
        kd=KD,
    )
    # ...snip&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The method is working! The ship now tends to keep its throttle in an
appropriate range. &lt;span&gt;\(K_p\)&lt;/span&gt; is controlling how &lt;i&gt;fast&lt;/i&gt; you want to
reach &lt;span&gt;\(h\)&lt;/span&gt; and &lt;span&gt;\(K_d\)&lt;/span&gt; is controlling how &lt;i&gt;ease&lt;/i&gt; reaching
&lt;span&gt;\(h\)&lt;/span&gt;.&lt;/p&gt;&lt;figure&gt;&lt;img src="/files/20230430/pd.png" alt="Floating at alt 180"/&gt;&lt;figcaption&gt;Floating at alt 180&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;However, the ship stuck at somewhere beneath &lt;span&gt;\(200\)&lt;/span&gt;. You pick up a pen
and try to figure out where it reaches the balance, that is
&lt;span&gt;\(\text{thrust}=\text{gravity}\)&lt;/span&gt;:&lt;/p&gt;&lt;div class="equation"&gt;\begin{align*}
f = mg \\
K_p (h-x) + 0 = mg \\
x = -\frac{mg}{K_p} + h
\end{align*}&lt;/div&gt;&lt;p&gt;&lt;span&gt;\(h-x = \frac{mg}{K_p}\)&lt;/span&gt;, which means that the rocket can never reach
target height &lt;span&gt;\(h\)&lt;/span&gt;!&lt;/p&gt;&lt;p&gt;&lt;b&gt;I&lt;/b&gt;&lt;/p&gt;&lt;p&gt;You have tried to taking &lt;span&gt;\(E\)&lt;/span&gt; and &lt;span&gt;\(E'\)&lt;/span&gt; into account however none
of them helped. What will you do if you observe your ship is not going to
reach &lt;span&gt;\(h\)&lt;/span&gt;? As time goes by you will gradually become impatient and
throttle up. How to measure &lt;i&gt;yourself losing patience&lt;/i&gt;? Yes that's
&lt;span&gt;\(\int E \mathrm{d}t\)&lt;/span&gt;! As usual, you snap a constant &lt;span&gt;\(K_i\)&lt;/span&gt; onto it
and hope this works.&lt;/p&gt;&lt;div class="equation"&gt;\[f = K_p E + K_d \frac{\mathrm{d}E}{\mathrm{d}t} + K_i \int^{t}_{0} E \mathrm{d}u\]&lt;/div&gt;&lt;p&gt;And you implement this in Python:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-python"&gt;import krpc, time

HEIGHT = 200
KP = 0.02
KI = 0.001
KD = 0.01
DELTA_T = 0.01


class Controller:
    def __init__(
            self,
            target: float,
            kp: float,
            kd: float,
            ki: float
    ) -&amp;gt; None:
        self.target = target
        self.kp = kp
        self.kd = kd
        self.ki = ki
        self.last_error = 0
        self.integral = 0

    def step(self, current: float, dt: float) -&amp;gt; float:
        error = self.target - current

        p = error * self.kp

        dE = (error - self.last_error) / dt
        d = self.kd * dE

        i = self.ki * self.integral

        self.last_error = error
        self.integral += error * dt

        return p + d + i


def main():
    conn = krpc.connect(name="KerbalHopper Controller")
    vessel = conn.space_center.active_vessel
    controller = Controller(
        target=HEIGHT,
        kp=KP,
        kd=KD,
        ki=KI,
    )

    input("Press enter to launch.")
    vessel.control.activate_next_stage()

    while True:
        flight = vessel.flight()
        alt = flight.surface_altitude
        throttle = controller.step(current=alt, dt=DELTA_T)
        vessel.control.throttle = throttle
        print(
            "Altitude: {alt} Throttle: {throttle}".format(
                alt=alt,
                throttle=throttle,
            )
        )
        time.sleep(DELTA_T)


if __name__ == "__main__":
    main()&lt;/code&gt;&lt;/pre&gt;&lt;figure&gt;&lt;img src="/files/20230430/pid.png" alt="Floating at alt 200"/&gt;&lt;figcaption&gt;Floating at alt 200&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;And there you go! Your hard work has paid off. The ship is now floating at
&lt;span&gt;\(200\)&lt;/span&gt; meters above the ground. You can now sit back and relax until
it runs out of fuel!&lt;/p&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;&lt;p&gt;It's such a weird method with some nice symmetrical properties.
&lt;span&gt;\(K_p E\)&lt;/span&gt; controls this moment, &lt;span&gt;\(K_d \frac{\mathrm{d}E}{\mathrm{d}t}\)&lt;/span&gt;
predicts the future and &lt;span&gt;\(K_i \int E \mathrm{d}t\)&lt;/span&gt; reflects the past. You
are fascinated by how easy and effective this 28-line of code is - it made
no assumptions with the system you are interacting. The same laws still
make sense on Duna and Eve. You decide to name it &lt;b&gt;PID&lt;/b&gt;, taking the
first letter of &lt;b&gt;P&lt;/b&gt;roposal, &lt;b&gt;I&lt;/b&gt;ntegral and &lt;b&gt;D&lt;/b&gt;erivative.&lt;/p&gt;&lt;p&gt;...and you find out frustratingly that it was invented in 1940s. There's
even a &lt;a href="https://en.wikipedia.org/wiki/PID_controller"&gt;Wikipedia page&lt;/a&gt;
for it.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Source code of this article can be found
&lt;a href="https://github.com/yikerman/simple-kerbalhopper"&gt;here&lt;/a&gt;.&lt;/b&gt;&lt;/p&gt;</content></entry><entry><title>An Introduction to Lambda Calculus</title><link rel="alternate" href="https://ycao.net/posts/an-introduction-to-lambda-calculus.html"/><id>https://ycao.net/posts/an-introduction-to-lambda-calculus.html</id><updated>2023-02-09T00:00:00Z</updated><content type="html">&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;&lt;p&gt;Who doesn't like simplicity! To me simplicity is not only a preference but
a belief. I always has a werid feeling that only the simple things last.&lt;/p&gt;&lt;p&gt;In the world of calculation, the most simple thing might be a Turing
machine, a mathematical model of computation that is capable of
implementing any algorithm using a tape, a head, a state and instructions.
However besides the tape stuff, another extremely simple mathematical
model was also invented at roughly the same time. Don't be surprised when
you see its name: &lt;b&gt;&lt;i&gt;Lambda calculus&lt;/i&gt;&lt;/b&gt; (&lt;span&gt;\(\lambda\)&lt;/span&gt;-calculus). It
consists a set of notation system and reduction rules. Compared with the
Turing machine, it is more "math-y" than "computer-y".&lt;/p&gt;&lt;h2 id="notation-system"&gt;Notation System&lt;/h2&gt;&lt;p&gt;&lt;i&gt;I've used it!&lt;/i&gt; you may think. Correct! Lambda calculus is the
fundamental building blocks in Functional Programming language. It's
extremely likely to meet it in most modern programming languages, for
example Python, JavaScript and even OO languages such as
&lt;a href="https://stackoverflow.com/questions/48210733/link-between-lambda-calculus-and-lambda-expressions-in-c"&gt;C++&lt;/a&gt;
and Java. Let's take a look at an example:&lt;/p&gt;&lt;div class="equation"&gt;\[\lambda x . x\]&lt;/div&gt;&lt;p&gt;A little bit confused huh? Let's write it in Python:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-python"&gt;lambda x: x&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Much more intuitive now! It simply outputs whatever is inputed.&lt;/p&gt;&lt;p&gt;It can be applied to another expression by writing the expresstion after
the function:&lt;/p&gt;&lt;div class="equation"&gt;\[(\lambda x.x) y\]&lt;/div&gt;&lt;p&gt;And it resolves into &lt;span&gt;\(y\)&lt;/span&gt;. We'll cover the details later.&lt;/p&gt;&lt;p&gt;This is one of the so-called &lt;i&gt;lambda expressions&lt;/i&gt;. There are three
kinds of lambda expressions:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Name: just a trivial name representing anything. e.g. &lt;span&gt;\(x\)&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Abstraction: &lt;span&gt;\(\lambda param . body\)&lt;/span&gt;, where &lt;span&gt;\(param\)&lt;/span&gt; is a
name and body is the substitution rule. e.g. &lt;span&gt;\(\lambda xy.xyx\)&lt;/span&gt; (which
is in infact an abbreviation of &lt;span&gt;\(\lambda x.\lambda y.xyx\)&lt;/span&gt;)&lt;/li&gt;&lt;li&gt;Application: a list of expressions. e.g. &lt;span&gt;\((\lambda xz.xxz)ij\)&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;You can check out the formal definition at
&lt;a href="https://en.wikipedia.org/wiki/Lambda_calculus#Formal_definition"&gt;Wikipedia&lt;/a&gt;.
It's totally fine to treat the lambda expression notation system as a
minimal programming language whose keywords are only &lt;span&gt;\(.\)&lt;/span&gt; (dot) and
&lt;span&gt;\(\lambda\)&lt;/span&gt; (lambda). There are also optional quotes in lambda
expressions. Without quotes we phrase the expression from left to right.&lt;/p&gt;&lt;p&gt;In an abstraction, or function, there're two types of variables:
&lt;i&gt;bounded&lt;/i&gt; and &lt;i&gt;free&lt;/i&gt; variables. A variable is &lt;i&gt;bounded&lt;/i&gt; means it
appears in the "params" section and &lt;i&gt;free&lt;/i&gt; is the opposite. This is
similar to local and global variables. For example in &lt;span&gt;\((\lambda x.xy)\)&lt;/span&gt;,
&lt;span&gt;\(x\)&lt;/span&gt; is bounded and &lt;span&gt;\(y\)&lt;/span&gt; is free.&lt;/p&gt;&lt;h2 id="reduction-rules"&gt;Reduction Rules&lt;/h2&gt;&lt;p&gt;The reduction rules have horrible names: &lt;span&gt;\(\alpha\)&lt;/span&gt;-conversion and
&lt;span&gt;\(\beta\)&lt;/span&gt;-reduction.&lt;/p&gt;&lt;p&gt;&lt;span&gt;\(\alpha\)&lt;/span&gt;-conversion is very stupid: a name can be written in another
if its bounded. For exmaple &lt;span&gt;\(\lambda x.xy\)&lt;/span&gt; is equivalent to
&lt;span&gt;\(\lambda z.zy\)&lt;/span&gt;.&lt;/p&gt;&lt;p&gt;&lt;span&gt;\(\beta\)&lt;/span&gt;-reduction is also a dumb operation: it means substituting
using the rules defined in the body of the function. For example, resolve
the application:&lt;/p&gt;&lt;div class="equation"&gt;\[(\lambda x.x) y = [y/x] x = y\]&lt;/div&gt;&lt;p&gt;The &lt;span&gt;\([a/b]\)&lt;/span&gt; mark simply means substituting &lt;span&gt;\(b\)&lt;/span&gt; with &lt;span&gt;\(a\)&lt;/span&gt;. Now
try a more complex one:&lt;/p&gt;&lt;div class="equation"&gt;\begin{align*}
&amp;amp; (\lambda xy.xyx)ab \\
&amp;amp; = (((\lambda x . \lambda y . xyx)) a ) b \\
&amp;amp; = (\boxed{[a/x]} (\lambda y . \boxed{x} y \boxed{x})) b \\
&amp;amp; = (\lambda y . \boxed{a} y \boxed{a}) b \\
&amp;amp; = \boxed{[b/y]} a \boxed{y} a = a \boxed{b} a
\end{align*}&lt;/div&gt;&lt;p&gt;Still confused? Try it out in Python.&lt;/p&gt;&lt;pre&gt;&lt;code class="language-python"&gt;(lambda x: (lambda y: x + y + x))('a')('b')
# =&amp;gt; 'aba'&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Of course no one will write such horrible program. Let's try naming it
(however few real lambda functions are given names):&lt;/p&gt;&lt;pre&gt;&lt;code class="language-python"&gt;def l1(x):
    def l2(y):
        return x + y + x
    return l2

l('a')
# =&amp;gt; &amp;lt;function __main__.l1.&amp;lt;locals&amp;gt;.l2(y)&amp;gt;
l('a')('b')
# =&amp;gt; 'b'&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note that for &lt;code&gt;l2&lt;/code&gt; (the inner lambda function) variable &lt;span&gt;\(x\)&lt;/span&gt; is
free. However for the whole expression it's bounded.&lt;/p&gt;&lt;h2 id="arithmetic"&gt;Arithmetic&lt;/h2&gt;&lt;p&gt;Time to do some calculations! Let's start by defining &lt;span&gt;\(0\)&lt;/span&gt;.&lt;/p&gt;&lt;div class="equation"&gt;\[\lambda sz.z\]&lt;/div&gt;&lt;p&gt;And yes! &lt;span&gt;\(0\)&lt;/span&gt; is a function! Try it out:&lt;/p&gt;&lt;div class="equation"&gt;\[(\lambda sz.z) a = (\lambda s . (\lambda z.z)) a = [a/s](\lambda z.z) = \lambda z.z\]&lt;/div&gt;&lt;p&gt;The input &lt;span&gt;\(a\)&lt;/span&gt; is thrown away, leaving only &lt;span&gt;\(\lambda z.z\)&lt;/span&gt; which is
called a "identity function". There're also many other ways defining
&lt;span&gt;\(0\)&lt;/span&gt;, but there are
&lt;a href="https://stackoverflow.com/a/1485145/10811334"&gt;good reasons&lt;/a&gt; using
this. Just keep going by defining &lt;span&gt;\(1, 2, ...\)&lt;/span&gt; and all the natural
numbers.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Successor&lt;/b&gt;&lt;/p&gt;&lt;p&gt;One approach to this is to define a "successor operation", which basically
returns the number that is one greater than the input. It goes as follows
(yes we are giving it a name since it's quite common):&lt;/p&gt;&lt;div class="equation"&gt;\[\mathbf{S} = \lambda wyx.y(wyx)\]&lt;/div&gt;&lt;p&gt;Actually I prefer the form of:&lt;/p&gt;&lt;div class="equation"&gt;\[\mathbf{S} = \lambda w . (\lambda yx.y(wyx))\]&lt;/div&gt;&lt;p&gt;Apply our &lt;span&gt;\(0\)&lt;/span&gt; to it:&lt;/p&gt;&lt;div class="equation"&gt;\begin{align*}
&amp;amp; \mathbf{S} 0 \\
&amp;amp; = \mathbf{S} (\lambda sz.z) \\
&amp;amp; = (\lambda wyx.y(wyx))(\lambda sz.z) \\
&amp;amp; = \boxed{[\lambda sz.z / w]} (\lambda yx.y(\boxed{w} y x)) \\
&amp;amp; = \lambda yx.y(\boxed{(\lambda sz.z)} yx) \\
&amp;amp; = \lambda yx.y((\lambda z.z) x) \\
&amp;amp; = \lambda yx.y(x) = \lambda sz.s(z) = 1
\end{align*}&lt;/div&gt;&lt;p&gt;Another really weird function huh? Compared with &lt;span&gt;\(0\)&lt;/span&gt;, &lt;span&gt;\(z\)&lt;/span&gt; is
quoted by &lt;span&gt;\(s\)&lt;/span&gt; in &lt;span&gt;\(1\)&lt;/span&gt; and that's how we encode out natural
numbers. Recall that this is not the only way to encode natural numbers,
but we find defining calculations for it much eaiser (covered later).
Rewrite it in Python if you are still confused:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-python"&gt;def zero(s):
    return lambda z: z


def S(w):
    # Define function "inner" for the sake of less nasty lambda nesting.
    def inner(y):
        # We know that w is a function, hence we're going to call it instead of join (+) it.
        return lambda x: y + w(y)(x)
    return inner&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since &lt;span&gt;\(w\)&lt;/span&gt; is always in the form of
&lt;span&gt;\(\lambda sz.s(s(s( ... (z))))\)&lt;/span&gt;, by calling &lt;span&gt;\(w\)&lt;/span&gt; using &lt;span&gt;\((wyx)\)&lt;/span&gt;
we "unwrap" the head of &lt;span&gt;\(w\)&lt;/span&gt; and "de-function" it. The &lt;span&gt;\(y\)&lt;/span&gt; at the
head of &lt;span&gt;\(\boxed{y}(wyx)\)&lt;/span&gt; adds another layer of nesting.&lt;/p&gt;&lt;p&gt;Now you have an idea what it is doing. Now try to get &lt;span&gt;\(2\)&lt;/span&gt;:&lt;/p&gt;&lt;div class="equation"&gt;\begin{align*}
&amp;amp; \mathbf{S} 1 = (\lambda \boxed{w}yx.y(\boxed{w}yx))\boxed{(\lambda yx.y(x))} \\
&amp;amp; = \lambda yx.y(\boxed{(\lambda sz.s(z))}yx) \\
&amp;amp; = \lambda yx.y(y(x)) \\
&amp;amp; = \lambda sz.s(s(z)) = 2
\end{align*}&lt;/div&gt;&lt;p&gt;Note that we have renamed the variables for clarity.&lt;/p&gt;&lt;p&gt;Each time &lt;span&gt;\(\mathbf{S}\)&lt;/span&gt; is applied, the nesting goes deeper. We can
even test it in Python!&lt;/p&gt;&lt;pre&gt;&lt;code class="language-python"&gt;zero('s')('z')
# =&amp;gt; 'z'
one = S(zero)
one('s')('z')
# =&amp;gt; 'sz'
two = S(one)
two('s')('z')
# =&amp;gt; 'ssz'&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Addition&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Addition can also be achieved by the successor function. Write a number
before &lt;span&gt;\(\mathbf{S}\)&lt;/span&gt;:&lt;/p&gt;&lt;div class="equation"&gt;\begin{align*}
&amp;amp; 2\mathbf{S} \\
&amp;amp; = (\lambda s . \lambda z . s (s(z))) \mathbf{S} \\
&amp;amp; = \lambda z.\mathbf{S}(\mathbf{S}(z))
\end{align*}&lt;/div&gt;&lt;p&gt;It's resolved into a function with &lt;span&gt;\(2\)&lt;/span&gt; successor operations! Now it's
trivial to calculate &lt;span&gt;\(2+3\)&lt;/span&gt; using it:&lt;/p&gt;&lt;div class="equation"&gt;\[2\mathbf{S}3 = \mathbf{S}\mathbf{S}3 = \mathbf{S}4 = 5\]&lt;/div&gt;&lt;p&gt;Numbers defined in a recursive way make this operation a breeze.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Multiplication&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Multiplication is also made easy by the defination of numbers.&lt;/p&gt;&lt;div class="equation"&gt;\[\mathbf{M} = \lambda xyz.x(yz)\]&lt;/div&gt;&lt;p&gt;It "unwraps" &lt;span&gt;\(y\)&lt;/span&gt; and apply the repeated sequence to &lt;span&gt;\(x\)&lt;/span&gt;, say &lt;span&gt;\(2 \times 3\)&lt;/span&gt;:&lt;/p&gt;&lt;div class="equation"&gt;\begin{align*}
&amp;amp; (\mathbf{M}2)3 = (\lambda xyz.x(yz)2)3 \\
&amp;amp; = \lambda z.2(3z) \\
&amp;amp; = \lambda z.(\lambda uw . u(u(w)))((\lambda ij.i(i(i(j))))z) \\
&amp;amp; = \lambda z.(\lambda uw . u(u(w)))(\lambda j.z(z(z(j)))) \\
&amp;amp; = \lambda z.(\lambda w . \boxed{\lambda j.z(z(z(j)))}(\boxed{\lambda j.z(z(z(j)))}(w))) \\
&amp;amp; = \lambda z.(\lambda w . z(z(z(z(z(z(w))))))) \\
&amp;amp; = \lambda s. \lambda z . s(s(s(s(s(s(z)))))) = 6 \\
\end{align*}&lt;/div&gt;&lt;p&gt;Whoa so many &lt;i&gt;brackets&lt;/i&gt;! But trust me it's doing the right thing.&lt;/p&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;p&gt;You should have a rough understanding of Lambda Calculus now. It is a
simple yet powerful system and there's still a lot to learn. You can:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Try to create more arithmetic operations using the lambda system.&lt;/li&gt;&lt;li&gt;Check out &lt;a href="https://en.wikipedia.org/wiki/Lambda_calculus"&gt;Wikipedia&lt;/a&gt;
for a more systematic and formal introduction.&lt;/li&gt;&lt;li&gt;Try some FP languages such as &lt;a href="https://lisp-lang.org/"&gt;Lisp&lt;/a&gt;
and &lt;a href="https://www.haskell.org/"&gt;Haskell&lt;/a&gt;. (After all that's what
this is all for!)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Good luck and have fun!&lt;/p&gt;</content></entry><entry><title>匀速圆周运动加速度的推导</title><link rel="alternate" href="https://ycao.net/posts/uniform-circular-motion-acceleration.html"/><id>https://ycao.net/posts/uniform-circular-motion-acceleration.html</id><updated>2022-07-12T00:00:00Z</updated><content type="html">&lt;p&gt;人教版的物理必修二中跳过了对匀速圆周运动的向心力的推导，用&lt;code&gt;精确的实验表明，向心力的大小可以表示为...或...&lt;/code&gt;这一套模糊的说辞蒙混过关了。这篇文章旨在帮助高中生理解公式的来历。&lt;/p&gt;&lt;p&gt;考虑一个物体在圆周上运动。我们记圆周运动的半径为&lt;span&gt;\(r\)&lt;/span&gt;，线速率为&lt;span&gt;\(v\)&lt;/span&gt;，角速度为&lt;span&gt;\(\omega\)&lt;/span&gt;，&lt;span&gt;\(0\)&lt;/span&gt;和&lt;span&gt;\(t\)&lt;/span&gt;时刻的线速度为&lt;span&gt;\(\vec{v_{i}}\)&lt;/span&gt;和&lt;span&gt;\(\vec{v_{j}}\)&lt;/span&gt;，经过的角度为&lt;span&gt;\(\alpha\)&lt;/span&gt;，易知&lt;span&gt;\(\lvert \vec{v_{i}} \rvert = \lvert \vec{v_{j}} \rvert = v\)&lt;/span&gt;，且都与圆相切。&lt;/p&gt;&lt;p&gt;这一段时间中的平均加速度为&lt;/p&gt;&lt;div class="equation"&gt;\[\vec{a} = \frac{\vec{v_{j}}-\vec{v_{i}}}{t}\]&lt;/div&gt;&lt;p&gt;当&lt;span&gt;\(t\rightarrow 0\)&lt;/span&gt;时候&lt;span&gt;\(\vec{a}\)&lt;/span&gt;为瞬时加速度。&lt;/p&gt;&lt;figure&gt;&lt;img src="/files/20220712/1.png"/&gt;&lt;/figure&gt;&lt;p&gt;可以看到在式子中我们要将两个向量相减。我们可以通过平移把两个向量尾尾相接便于计算。由切线的性质，可以发现平移后的&lt;span&gt;\(\vec{v_{i}'}\)&lt;/span&gt;和&lt;span&gt;\(\vec{v_{j}'}\)&lt;/span&gt;夹角&lt;span&gt;\(\alpha_{1}=\alpha\)&lt;/span&gt;。&lt;/p&gt;&lt;figure&gt;&lt;img src="/files/20220712/2.png"/&gt;&lt;/figure&gt;&lt;p&gt;我们可以在图片中画出&lt;span&gt;\(\overrightarrow{\triangle v}=\vec{v_{j}'}-\vec{v_{i}'}\)&lt;/span&gt;。不熟悉向量减法的可以用&lt;span&gt;\(\overrightarrow{\triangle v}+\vec{v_{i}'}=\vec{v_{j}'}\)&lt;/span&gt;思考，即&lt;span&gt;\(\vec{v_{i}'}\)&lt;/span&gt;加上一个向量等于&lt;span&gt;\(\vec{v_{j}'}\)&lt;/span&gt;。&lt;/p&gt;&lt;figure&gt;&lt;img src="/files/20220712/3.png"/&gt;&lt;/figure&gt;&lt;p&gt;接下来一步至关重要。由于&lt;span&gt;\(AB=AC\)&lt;/span&gt;，&lt;span&gt;\(DE=DF\)&lt;/span&gt;，&lt;span&gt;\(\alpha_{1}=\alpha\)&lt;/span&gt;，可以证明&lt;span&gt;\(\triangle ABC \sim \triangle DEF\)&lt;/span&gt;。所以&lt;/p&gt;&lt;div class="equation"&gt;\[\frac{\lvert \overrightarrow{\triangle v} \rvert}{\lvert \vec{v_{i}'} \rvert} = \frac {BC} {AB} = \frac {BC} {r}\]&lt;/div&gt;&lt;p&gt;由于我们在求解瞬时加速度，&lt;span&gt;\(t\rightarrow 0\)&lt;/span&gt;时&lt;span&gt;\(\alpha \rightarrow 0\)&lt;/span&gt;，&lt;span&gt;\(BC \rightarrow \overset{\LARGE\frown}{BC}\)&lt;/span&gt;，所以&lt;/p&gt;&lt;div class="equation"&gt;\[\frac{\lvert \overrightarrow{\triangle v} \rvert}{\lvert \vec{v_{i}'} \rvert} = \frac {\overset{\LARGE\frown}{BC}} {r} = \frac {\alpha r} {r} = \alpha\]&lt;/div&gt;&lt;div class="equation"&gt;\[\lvert \overrightarrow{\triangle v} \rvert = \alpha \lvert \vec{v_{i}'} \rvert = \alpha v\]&lt;/div&gt;&lt;figure&gt;&lt;img src="/files/20220712/4.png"/&gt;&lt;/figure&gt;&lt;p&gt;将得到的&lt;span&gt;\(\lvert \overrightarrow{\triangle v} \rvert\)&lt;/span&gt;代入&lt;span&gt;\(\vec{a}\)&lt;/span&gt;，可以求出瞬时加速度的大小&lt;/p&gt;&lt;div class="equation"&gt;\[\lvert \vec{a} \rvert = \lvert \frac{\vec{v_{j}}-\vec{v_{i}}}{t} \rvert = \frac{\lvert \overrightarrow{\triangle v} \rvert}{t} = \frac{\alpha v}{t}\]&lt;/div&gt;&lt;p&gt;由线速度和角速度的定义，可以得到&lt;/p&gt;&lt;div class="equation"&gt;\[\lvert \vec{a} \rvert = v \omega\]&lt;/div&gt;&lt;p&gt;但这只是加速度的大小。观察以下图像：&lt;/p&gt;&lt;figure&gt;&lt;img src="/files/20220712/5.png"/&gt;&lt;/figure&gt;&lt;p&gt;易得当&lt;span&gt;\(t \rightarrow 0\)&lt;/span&gt;时&lt;span&gt;\(\alpha_{1} \rightarrow 0\)&lt;/span&gt;，此时&lt;span&gt;\(\overrightarrow{\triangle v}\)&lt;/span&gt;与圆的切线垂直，即&lt;span&gt;\(\vec{a}\)&lt;/span&gt;朝向圆心。&lt;/p&gt;</content></entry><entry><title>Invent wheels with Autotools &amp; C</title><link rel="alternate" href="https://ycao.net/posts/invent-wheels-with-autotools-c.html"/><id>https://ycao.net/posts/invent-wheels-with-autotools-c.html</id><updated>2020-08-08T00:00:00Z</updated><content type="html">&lt;p&gt;This how-to guide will teach you how to invent wheels with Autotools &amp;amp; C.
Note that it isn't detailed, just to give you some ideas how the whole
system works.&lt;/p&gt;&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;&lt;p&gt;&lt;b&gt;Requirements&lt;/b&gt;&lt;/p&gt;&lt;p&gt;My Autotools versions are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Automake 1.16.1&lt;/li&gt;&lt;li&gt;Autoconf 2.69&lt;/li&gt;&lt;li&gt;Libtool 2.4.6&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;And I'm on OS X. Installation guide will not be included.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Product&lt;/b&gt;&lt;/p&gt;&lt;p&gt;We'll make a simple C lib (C++ compatible) called &lt;code&gt;libts&lt;/code&gt; helps you
to get time duration between two function calls.&lt;/p&gt;&lt;h2 id="procedures"&gt;Procedures&lt;/h2&gt;&lt;p&gt;Time to actually make something!&lt;/p&gt;&lt;p&gt;&lt;b&gt;A simple lib&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Our project starts simply like this, including a header file and a source
file:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-plaintext"&gt;.
├── ts.c
└── ts.h

0 directories, 2 files&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And codes are shown below:&lt;/p&gt;&lt;p&gt;&lt;code&gt;ts.h&lt;/code&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class="language-c"&gt;#ifndef __TS_H__
#define __TS_H__

#include &amp;lt;sys/time.h&amp;gt;
#include "config.h" /* This file will be generated later */

/* For C++ compatiblity */
#ifdef __cplusplus
extern "C" {
#endif

#define FIRST_CALL -1.0

/*
* Returns the time passed in seconds before the latest call.
* If it's the first time called, return FIRST_CALL.
*/
extern double getTimeDuration(void);

/* End of the extern "C" above */
#ifdef __cplusplus
}
#endif

#endif /* __TS_H__ */&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;ts.c&lt;/code&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class="language-c"&gt;#include "ts.h"

double getTimeDuration()
{
    static double latest = 0; /* Last call */
    double sec; /* Current time in second */
    double ret; /* Return value */

    #if HAVE_GETTIMEOFDAY /* This macro comes from config.h */

    /* In some specific OS, gettimeofday() is available */
    /* See https://man7.org/linux/man-pages/man2/gettimeofday.2.html */

    struct timeval tv;
    gettimeofday(&amp;amp;tv, NULL);
    sec = tv.tv_sec;
    sec += tv.tv_usec / 1000000.0;

    #else /* HAVE_GETTIMEOFDAY */

    /* Or we can use time() instead. */

    sec = time(NULL);

    #endif /* HAVE_GETTIMEOFDAY */

    ret = sec - latest; /* Calculate difference */
    latest = sec; /* Update latest */
    if (ret == sec) /* First call, return special value */
        return FIRST_CALL;
    else
        return ret;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now the source code is done. Let's setup Autotools!&lt;/p&gt;&lt;p&gt;&lt;b&gt;Autotools&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Autotools is a complicated build system. We have to create several files.&lt;/p&gt;&lt;p&gt;&lt;i&gt;Note: The following commands are run at the root of the source code.&lt;/i&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;code&gt;configure.ac&lt;/code&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;code&gt;configure.ac&lt;/code&gt; is a file for &lt;code&gt;Autoconf&lt;/code&gt; to generate an
&lt;code&gt;configure&lt;/code&gt; script. It checks availability (in our example, if
&lt;code&gt;gettimeofday()&lt;/code&gt; and &lt;code&gt;time()&lt;/code&gt; are available) and generates
&lt;code&gt;Makefile&lt;/code&gt; from &lt;code&gt;Makefile.in&lt;/code&gt;, which will be generated later.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Let's start with an &lt;code&gt;autoscan&lt;/code&gt; GNU provided. It scans your code and
generates an &lt;code&gt;configure.ac&lt;/code&gt; automatically.&lt;/p&gt;&lt;pre&gt;&lt;code class="language-bash"&gt;autoscan&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The directory should be like this:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-plaintext"&gt;.
├── autoscan.log
├── configure.scan
├── ts.c
└── ts.h

0 directories, 4 files&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;autoscan.log&lt;/code&gt; can be removed safely. What matters is
&lt;code&gt;configure.scan&lt;/code&gt;. We have to rename it to &lt;code&gt;configure.ac&lt;/code&gt; first:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-bash"&gt;rm -f autoscan.log
mv configure.scan configure.ac&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;configure.ac&lt;/code&gt; looks like this:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-bash"&gt;#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.69])
AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])
AC_CONFIG_SRCDIR([ts.c])
AC_CONFIG_HEADERS([config.h])

# Checks for programs.
AC_PROG_CC

# Checks for libraries.

# Checks for header files.
AC_CHECK_HEADERS([sys/time.h])

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.
AC_CHECK_FUNCS([gettimeofday])

AC_OUTPUT&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It's actually a piece of &lt;a href="https://www.gnu.org/software/m4/m4.html"&gt;m4&lt;/a&gt;
language, and all those &lt;code&gt;AC_XXX&lt;/code&gt; stuffs are macros and will be
expanded into bash scripts. You can write bash in the &lt;code&gt;configure.ac&lt;/code&gt;
directly as well.&lt;/p&gt;&lt;p&gt;As you can see, it's smart to include &lt;code&gt;AC_CHECK_FUNCS([gettimeofday])&lt;/code&gt;.
This will checks if &lt;code&gt;gettimeofday&lt;/code&gt; is available. Magic! But, we have
to modify it anyway.&lt;/p&gt;&lt;pre&gt;&lt;code class="language-bash"&gt;#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.69]) # 1
AC_INIT([libts], [0.1], [username@example.com]) # 2
AC_CONFIG_SRCDIR([ts.c]) # 3
AC_CONFIG_HEADERS([config.h]) # 4

AM_INIT_AUTOMAKE # Modified 5

# Checks for programs.
AC_PROG_CC # 6

# Checks for libraries.

# Checks for header files.
AC_CHECK_HEADERS([sys/time.h]) # 7

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.
AC_CHECK_FUNCS([gettimeofday]) # 8

LT_INIT # Modified 9
AC_CONFIG_FILES([Makefile]) # Modified 10

AC_OUTPUT # 11&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;1: Checks the minimal version of &lt;code&gt;autoconf&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;2 &amp;amp; 11: Start and end of every &lt;code&gt;configure.ac&lt;/code&gt;. It also includes
some info for your project.&lt;/li&gt;&lt;li&gt;3: Check if the source code exists.&lt;/li&gt;&lt;li&gt;4: Generates the configuration header named &lt;code&gt;config.h&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;5: Prepare for generating &lt;code&gt;Makefile&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;6: Determine a C compiler to use.&lt;/li&gt;&lt;li&gt;7: Check if header file &lt;code&gt;sys/time.h&lt;/code&gt; is available.&lt;/li&gt;&lt;li&gt;8: Check if function &lt;code&gt;gettimeofday&lt;/code&gt; is available.&lt;/li&gt;&lt;li&gt;9: Initialize Libtool. This will be used later.&lt;/li&gt;&lt;li&gt;10: Generate &lt;code&gt;Makefile&lt;/code&gt; from &lt;code&gt;Makefile.in&lt;/code&gt;, which will be
generated later.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;And to generate the &lt;code&gt;configure&lt;/code&gt; file:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-bash"&gt;aclocal
autoconf
autoheader&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And your project will be like this:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-plaintext"&gt;.
├── aclocal.m4
├── autom4te.cache
│   ├── output.0
│   ├── output.1
│   ├── output.2
│   ├── output.3
│   ├── requests
│   ├── traces.0
│   ├── traces.1
│   ├── traces.2
│   └── traces.3
├── config.h.in
├── configure
├── configure.ac
├── ts.c
└── ts.h&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;&lt;code&gt;Makefile.am&lt;/code&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;code&gt;Makefile.am&lt;/code&gt; is a file for &lt;code&gt;automake&lt;/code&gt; to generate the
&lt;code&gt;Makefile.in&lt;/code&gt; mentioned above. Now create a &lt;code&gt;Makefile.am&lt;/code&gt; and
write the following stuffs:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-makefile"&gt;AUTOMAKE_OPTIONS = foreign
include_HEADERS=ts.h
lib_LTLIBRARIES = libts.la
libts_la_SOURCES=ts.c&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The build target is &lt;code&gt;libts.la&lt;/code&gt;, containing the source file
&lt;code&gt;ts.c&lt;/code&gt;, which uses &lt;code&gt;Libtool&lt;/code&gt; to sustain portability. It's
simpler than &lt;code&gt;configure.ac&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Also, note that &lt;code&gt;AUTOMAKE_OPTIONS&lt;/code&gt; is set to &lt;code&gt;foreign&lt;/code&gt;, so it
won't force us to create those &lt;code&gt;NEWS&lt;/code&gt;, &lt;code&gt;AUTHOR&lt;/code&gt;,
&lt;code&gt;ChangeLog&lt;/code&gt;, etc.&lt;/p&gt;&lt;p&gt;To generate &lt;code&gt;Makefile.in&lt;/code&gt;, run:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-bash"&gt;libtoolize # Generate supporting files for Libtool
automake --add-missing&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Tests&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Tests are always needed. Let's do this in Autotools' way. First create
&lt;code&gt;test.c&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-c"&gt;#include "ts.h"
#include &amp;lt;assert.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;

/* Program exits with 0 means tests has passed */
int main(int args, char *argv[])
{
    double first = getTimeDuration();
    assert(first == FIRST_CALL);
    double second = getTimeDuration();
    assert(second != FIRST_CALL);
    puts("OK");
    return 0;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can use modern test frameworks too.&lt;/p&gt;&lt;p&gt;&lt;code&gt;Makefile.am&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-makefile"&gt;# Lib
AUTOMAKE_OPTIONS = foreign
include_HEADERS=ts.h
lib_LTLIBRARIES = libts.la
libts_la_SOURCES=ts.c

# Tests
TESTS = checkTS
check_PROGRAMS = checkTS
checkTS_SOURCES = test.c
checkTS_LDFLAGS = libts.la&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Also, if you don't want to type the above &lt;code&gt;aclocal&lt;/code&gt; and stuff again:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-bash"&gt;autoreconf -i&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;configure &amp;amp; build&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Simple! Everything is ready now. Do this as usual:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-bash"&gt;./configure
make&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And to install:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-bash"&gt;sudo make install&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To test:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-bash"&gt;make check&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To make a distribution package:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-bash"&gt;make dist&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Whoa, you did that! To use this lib in your own programs, just
&lt;code&gt;#include &amp;lt;ts.h&amp;gt;&lt;/code&gt; and link this library (&lt;code&gt;-lts&lt;/code&gt;)!&lt;/p&gt;&lt;p&gt;&lt;b&gt;Product&lt;/b&gt;&lt;/p&gt;&lt;p&gt;This demo's distribution can be found
&lt;a href="/files/20200808/libts-0.1.tar.gz"&gt;here&lt;/a&gt;.&lt;/p&gt;</content></entry></feed>