问题描述

使用LaTex进行图片编辑时,在语法反复检查无误,相同语法使用不同图片显示成功的情况下,针对某几张图片使用\subfigure{}命令后,编译报错,错误解释为Package graphics: Division by 0

在使用vscode的图片预览功能时,并非正常显示图片,而是显示乱码

使用\figure对图片进行非拼接化排版时代码可以运行成功,但是在pdf中不会显示图片

问题解决

国内论坛对本问题产生原因的解释是“图片过大,超过251kb不可使用”,此为错误判断,LaTex可以放置大小为数个MB的EPS矢量图。
上查询到的问题产生原因是图片本身问题,而并非Tex的编译问题,这与笔者重复实验得出的结论一致,但我对其观点“如果能用preview软件打开则图片没有问题,通常是储存路径产生问题”抱有疑问,本次问题编译的原图片是可以使用windows自带图片查看器查看的,笔者使用其他资源方式下载同样图片,运用相同的存储路径后编译成功,故对其储存路径说法持怀疑态度,仍有疑问需要解答。

解决方法

以上问题为图片问题,更换图片下载源后编译成功

参考资料

stackexchange https://tex.stackexchange.com/questions/196036/odd-graphics-error-division-by-0

摘要

本文基于重新学习变分法之后的思考写成的较为直白简单的总结,罗列了一部分泛函及变分法的基础定义,将变分与微分做了很浅显的比较,可能初学者更容易接受,而不是像我当时学习一样一头雾水。全文参考老大中老师《变分法基础》,欧斐君,梁建华老师《变分法及其应用》

导火线:变分法的起源及三个问题

变分法中,最速降线问题就像经典力学中牛顿的苹果一样经典,这是人类开始研究变分法的标志,该问题如下:
假设有位于同一平面内不同铅锤位置的A、B两点(A、B两点不在同一铅锤线上),试求一条路径,使得一个小球在仅受重力的作用下从A点到达B点的时间最短。
相应的该问题的解法以及建模方式已经被讨论烂了,本文在此不再多作赘述,在这里我更想就该问题浅显的谈谈变分法与微积分的不同之处。
该问题乍一看好像就是微积分中的最值问题,我们有一函数,在某个范围内(满足一些约束条件)求函数的最值以及最值所处的点。但仔细看看,好像有一个条件不一样,我们是在约束范围内求对应数值最大的函数,变量从微积分中的标量或向量变为了函数,这样一下引出了三个问题:1.函数是如何对应的数值的(注意我们要选取一个对应最大数值的函数)2.函数是如何变化的?它有像微积分中$dx,dy$一样的无穷小量吗?3.我们可以像在为微积分中找最大值一样,对变量“求导”使其“导数”为零求得驻值从而得到对应数值的最值吗?
照例我们还是先解决第一个问题

泛函

要解决是一个函数对应一个数值的问题我们需要引入泛函这个概念。
为了便于理解,我们先做一个粗暴的解释,我们让一个函数$f(x)$与一个实数$A$相关联,用微积分中的话讲:我们构造由$f(x)$到$A$的映射,则$A$就称为函数$f(x)$的泛函,而$f(x)$则称为实数$A$的宗量。
我们现在再严格的从数学角度构造这个概念:
我们取一组有某种共同性质函数组成的集合,并成为函数类(集合的定义相信所有人都明白,你现在就需要讲集合中的元素由原本的实数换成一系列函数即可),我们称$F={f(x)}$是某一给定的函数类,而$R$就是实数集合,如果对于$F$中的每一个函数,都有$J \in R$按照一定的规律与之对应(注意这里J是个变量,它随着函数的改变而改变),则称$J$为函数$f(x)$的泛函,记作$J = J[f(x)]$,相应的$f(x)$则称为$J$的宗量。
按照上面的定义,我们需要寻求一个规律,将函数与数值相对应起来,我们第一个能想到的就是定积分(我们能得到定积分的前提条件是我们函数集中的所有函数都有相同的定义域,我们将在$(x_1, x_2)$上连续的函数称为连续函数集,记作$C[x_1, x_2]$)由此我们给出最简泛函的定义
若有函数$F(x, y(x), y’(x) )$是在区间$[x_1, x_2]$上有关独立变量$x, y(x), y’(x)$的函数(独立变量是指在该函数中以上三者都是相互独立的量,在之后的变分、求导中以该量为单独的变量,而不将$y(x)$看作是x的函数)则我们可以确定一个有关于$y(x)$的泛函$J(y(x))$为

其中被积函数$F$称作该泛函的核,这种泛函统称为最简泛函。

变分

我们再来解决第二个问题,函数之间的变化量可以像微积分中那样有像$dx, dy$一样的无穷小量吗,答案是肯定的。

函数间的距离

但在定义它之前我们先要来看看两个函数之间的距离该如何确定。我们假设有两个函数$y(x)$和$y_0(x)$,二者都在$[x_1,x_2]$上有n阶的连续导数,我们取二者在0到n阶导数之中最大的差值作为二者的距离,称为n级距离,当$n=0$时该距离被称为零级距离,我们用严格的数学公式表示它

我们由此可以很容易得到$d_0\leqslant d_1\leqslant d_2 \cdots \leqslant d_n$
我们类似复变函数中的定义,得到函数类中的邻域,即设有一在$[a,b]$上有n阶连续函数函数$y_0$,则相应的所有在$[a,b]$内n级距离小于$\delta$的函数类(一个集合)被称为$y_0$在$[a,b]$上的n级$\delta$邻域。我们可以用数学符号将其简练的表达为

我们易得,拥有更高阶$\delta$接近度的两个函数更加接近。

泛函的连续

当出现$\delta$时就应该警觉,因为它的出现往往暗示着无穷小量的定义,即经典的$\varepsilon-\delta$证明。(可以回想数学分析或高等数学中连续性证明)类似的,我们现在来定义泛函(注意是泛函而不是函数类)的连续。

对于任意给定的一个正数$\varepsilon$我们总是可以找到一个$\delta$使得
$y(x)\in \in N_n[\delta, y_0(x)]$且$y(x)\subset F$(其中F是泛函J的定义域)有
$|J(y(x))-J(y(x_0))|<\varepsilon$则称泛函J在$y_0(x)$处有n阶$\delta$接近度的连续泛函

最终我们来定义变分

注意,笔者在此对老大中老师《变分法》中有关变分的定义表示质疑,本节按照维基百科中有关变分的定义写成。
我们首先给出变分的定义,我们有在$[a,b]$上二阶导连续的函数$y_0(x)$我们定义一个有一阶导数的函数$\eta(x)$,其在边界上为零,即$\eta(a)=\eta(b)=0$取一趋近于零的无穷小量$\varepsilon$,则有$\varepsilon \eta(x)$为原函数$y_0(x)$的变分,记为$\delta y_0$。

另外给出等时变分的定义(这将是分析力学中重要部分),我们现在有坐标$x(t)$其是关于时间t的函数,我们根据上述可以得到x的变分就是任意一个满足在边界条件上为零的函数乘以一个无穷小量,原函数加上它就会变为另一个函数;等时变分则侧重另一个问题,即在同一时间(此时t为定值记为$t_0$)两个不同函数之差,我们可以记作$\varepsilon\eta(t_0)$,关于分析力学中虚位移的定义在变分上的定义,请移步《分析力学与变分法》

定义完了变分,我们来比较变分与微分;可以看到变分和微分都是我们对相应研究对象所取的无穷小量,函数中的变量$x$在加了$dx$后就不再是原来的点了,同理函数$y_0$在加了变分$\delta y_0$之后也发生了细微的变化变得不太一样。

总结,为什么变分法和微积分那么像?

稍稍总结一下我们现在所得到的,我们不难看出,泛函和函数之间十分类似,函数所拥有的定义经过变形加工就可以使用在泛函上。对于函数y(x)来说变量,或者研究对象就是自变量x,而对于泛函$J(y(x))$我们的研究对象或者自变量不再是一个值,而是y与x之间的关系,即函数。
冥冥之中自有天意,二者被联系在了一起,其实从数学角度出发,变分是微分的一种,是微分推广到无限空间上的形态。我们在这里不做过多追究,仅凭借一个不太严谨的例子,帮助各位粗浅理解一下。

想象我们有一一元函数,坐落在$x-y$平面上,于是我们有$y(x)$,即一个x就能确定一个y值,但是如果我告诉你,x并不是在平面上的点,而是有一定纵深的关于t的函数呢?这就相当于我们手里攥了数根电线,我们从电线的刨面看去,每一根电线就像是一个个在平面上的点一样,但实际上我们所看到的每一个点都是在空间中来回环绕的线

而我们之前所拥有的函数$y(x)$就正好将每一条“线”对应映射在了一个确定值上,这恰好满足了我们泛函中函数对数值的映射。综上我们可以粗浅理解为泛函就是将微积分中的点扩展在空间上无限长的线条,我们也可以解释,为什么泛函中的定义在微积分中都能找到相应的对照。

摘要

本篇作为2020年春季Functional Programming总结的第三篇,用全篇来总结迄今在这门课程中学到的最重要的思想:你写代码并告诉编译器运行的时候,编译器(以python的interpreter为例)它到底按照什么顺序,怎么跑的代码,或者说,计算机到底是怎么想的?照例,本文不重复课上的细节问题(因为我太菜,而人家写的太好),仅凭个人经验总结在有限时间学习到的东西,有错误在所难免,欢迎指出,接受批评。

一道复杂的脑经急转弯

我们看一组代码:声明!以下代码仅为了了解编译器运行方式,这是非常糟糕的代码,请不要在任何代码中采用以下形式

1
2
3
4
5
6
7
8
9
def horse(mask):
horse = mask
def mask(horse):
return horse
return horse(mask)

mask = lambda horse: horse(2)

horse(mask)

让我们抛出那个熟悉的疑问句:“What would python display?”
这是什么,我是谁,我在哪?这是大多数人看到这道题的第一反应(包括我),短短几行代码中有嵌套,有lambda函数,更要命的是,它将一个名称赋予了多个含义并且来回反转运用。
要想得到它最终的答案,我们还是从python的编译器的编译规则出发。

interpreter 的基础编译规则

我们在上一篇中其实大概的提到过,interpreter在遇到函数时是怎么办的,现在我们高度总结性概括一下:
1.当interpreter遇到一个用户自定义的函数时(即遇见def时),会首先将它的名字存储起来,并且表明这个名字代表一个函数,注意此时编译器不会鸟函数里面到底写了啥
2.根据第一条,因为interpreter完全不鸟你在函数的body即主体里到底写了啥,所以你在函数里面定义的函数(嵌套函数)不会被编译器像遇见的def函数一样保存。
3.lambda完全不用担心,因为当编译器发现它的时候,它就要被执行了(这句话你细品)。
4.函数的执行包括参数的输入,返回值,如果参数输入了,值还没返回,函数会等着直到值的返回。
5.局部变量永远都在局部环境下(上一节课说的盒子中)妄想透过这个盒子去往上一级环境。
6.参数是通过两级环境的通道。

我们再来看看这道可怕的题目

首先我们执行第一行代码,一个名为horse的函数被存在了全局变量下,但是它里面是什么,还没有人知道,接着我们跳过函数主体看到了第七行,看到mask代表了一个lambda函数,函数要输入一个叫horse的参量,我们也把他存在全局变量下

然后,函数被唤醒执行了!就在第九行!它执行的是horse函数并传递给它一个参量mask,是哪个mask?你看看现在我们有哪个mask在同一环境下的?没错就是那个代表lambda的函数,但注意,它没有被执行或者唤醒!我们如果想执行lambda函数应该召唤mask并给他一个参量我们没有给它参量,他就是被当作一个函数horse的参量传进函数主体中而已。记得我们的规则6吗?我们成功创建了一个环境,而mask由参量这个通道进入了该环境中。

接下来我们就该看horse函数内部了,第二行,我们做了一个神奇的附值。在这里有必要再强调一下附值的操作规则,附值是将等号左边的值传递给右边,也就是说等号的右边是先被计算的,还记得我们多个参数附值吗,想要调换两参数的值只用到了以下代码:

1
a, b = b, a

在该例子中,我们率先计算了等号右边的值,即将等号右边的名称表现为他们所代表的数值,然后再进行附值操作。回到正题第二行中的操作等于在该环境下,将mask这个代表lambda函数的值传递给了horse,现在我们在该环境下只要呼唤horse就等于呼唤mask

然后我们来到最最可怕的第三行。他干了什么,它重新定义了一个函数叫做mask且该函数的参数叫horse,注意!这个重新定义的函数和之前所有的值没有任何联系!
嘘!请安静,它依旧没有被唤醒,我们现在只是单纯定义了它,还记得我们在全局变量下定义函数会发生什么?我们只是将名称和函数联系到了一起,这样计算机在函数被执行时就知道去哪找被执行对象,我们会跳过该函数的主体,直接执行第五行。
与此同时发生了一件有趣的事情,在f1环境下,mask不再指代那个lambda函数了。一个环境下,一个名称只能指代一个东西,换位思考一下,你如果是计算机,当一个名字指代很多东西时,你当然会舍弃之前的指代关系,保留最新构建的框架。

我们来执行第五行,它干啥了?我们的函数f1要返回一个值,返回的是什么?horse(mask)还记得horse在该环境下(f1)代表什么吗?是那个我们一直在提但一直没叫起来的lambda函数!可以叫它起床了!我们对它传递了参量mask,它在这环境中指代啥来着?是我们刚刚在f1环境中定义的新函数啊!我们创建新的环境lambda并把参数扔进去。注意看lamnda的主体,我们扔进去的参量叫什么了?答案:horse,该horselambda下指代f1中的新函数mask


我们在lambda环境中返还的是什么?是lambda(f2)环境下的horse(2)这时发生了什么?我们把在f1环境下定义的新函数mask唤醒了并将参数2传递进去,于是我们建立了mask环境,该环境下函数的参量叫啥?horse。它等于啥?2。ok我们现在执行f1mask的主体。


f3环境执行,返回horse,我们参照构架图,该环境下horse = 2就是返回了2。问题来了它返回了值回哪去了?还记得规则4吗?在函数没有返回值时它一直等着,再看看我们之前唤醒的所有函数,f1,f2是不是都还在?回答我们最初的答案,谁叫醒的它,他就把值还到哪里去,于是f2的返还值为2,f1唤醒的f2f2把值还给f1f1的返还值为2。

恭喜,题目作结。

总结

在解决这个问题时我们都做了些什么?最最重要的就是环境构成图的思考方式,文中所有的图像皆取自lesson8 课件我们可以看到在运行一个程序时我们到底该如何使用框架与环境这两个重要因素,去换位思考。这种思考方式无论是在写代码或是debug中都极其有用,同样的它也会在后续的学习中帮助我们理解高级函数、递归、迭代等函数构成方式,也有助于我们更快速的检查阅读代码。
下次在coding或者debug的时候不妨问问自己,当计算机运行时在想些什么?

参考资料

https://inst.eecs.berkeley.edu/~cs61a/sp20/

摘要

本篇作为2020年春季Functional Programming总结的第二篇,内容大致包括python语言的环境(environment)框架(frame)特点,以及hw1、lab01中一些有趣题目的答案与反思。相关具体细节(例如函数的自定义等)原课程网上教科书已经编写的相当清楚,我就不再班门弄斧了,本篇目的是自己在学习过程中间的思考想法,仅供参考。希望你玩的愉快!

环境(environment)及框架(frame)

这是Founction Programming这门课程出现的第一个重要知识点,这种思考方式适用于任何编程环境任何编程化的语言
思考几个问题:当我们仅仅定义一个函数时发生了什么?当我们在python中定义一个函数,并在之后使用它时发生了什么?当我们从库中调用一个函数时发生了什么?如果我们定义的函数是嵌套的(nested),那我们在使用这个函数时发生了什么?
要理清这几个问题我们先搞清楚python的一个重要因素,环境(environment)
环境是指python在运行各个函数时的背景,它是由多个框架构成的。先不要着急理解它,引用课堂例子我们看如下代码:

1
2
from math import pi
pau = 2*pi

运行这个代码后,我们在全局下创造了一个盒子,我们首先从math库中将pi调用出来放进这个盒子中,然后根据我们的运算规则将相应的计算值赋予pau,同时把它也扔进盒子中。我们取课本原图表示这一环节。

这个盒子就是我们创造的全局环境,我们不管之后再添加什么代码,只要还在全局环境下,那么pi = 3.14....,而pau = 2pi,我们可以直接使用则两个名字代表数据。
我们再来看我们定义函数时会发生什么。有一定python基础的人(比如我)在不知道答案的时候往往会认为,一旦函数定义,python会将函数储存起来,形成一个自我的密闭空间。但事实并不是这样。
1
2
3
from math import mul
def square(x):
return mul(x, x)

我们在运行以上代码后,函数square并不会像我们想象的一样自动形成一个封闭的空间,而是跟我们之前的附值一样,将名称square保留在全局变量下,不会管它到底怎么运行,会返回什么,计算机只是将名称静置在全局环境中并且标明:这是一个函数,等待被召唤。引用课堂原图

而当我们使用我们所定义的函数的时候,函数会被唤醒,生成一个新的盒子,也就在此时我们函数的内容才会被interpreter审视进行相关的操作。值得注意的是,我们使用函数自己创造的盒子中的附值操作(assignment)都不会传递到任何的其他盒子中!附值操作只要出了这个盒子(环境)就消失了,不要妄图在盒子外使用自己在盒子内部的附值名称。
每一个盒子(除了全局)都有一个“母盒”(parent)你可以理解为新的盒子实在其之上建立的。如果你只是单纯定义了一个函数,那么该函数的“母盒”就是全局(global),如果是嵌套环境,则内部函数的母环境就是外部函数。每一个盒子内部的附值都不能拿出这个盒子,例如:
1
2
3
4
def example(x):
i = 1
return x + 1
i

程序会报错,因为i仅仅在函数内部定义,在出了函数之后就再无定义了。我们将这种数据(不光是附值,在一个自定义函数里定义的嵌套函数的名称拿到全局环境下也没有作用)称为局部变量(local variable)。
那么又有一个问题,母环境下的值可以在子环境中引用吗?可以用,但是请避免这种情况发生。在python2.x时代子环境,也就是我们说的子盒是没有权力调用除全局变量定义的附值的,而在python3.x时代这个问题似乎解决了,因为如果你尝试以下代码:
1
2
3
4
5
6
7
8
9
def example2(x):
i = 1
def sub():
return x + 1
return sub()

k = example2(1)
k
>>>2

计算机返还了2,也就是说子盒sub调用了在母盒中的附值i = 1,这一切看上去很完美,但是如果你尝试如下代码你会发现问题:
1
2
3
4
5
6
7
8
9
def count_cond(condition):
i , num = 1 , 0
def conts(n):
while i <= n:
if condition(n, i):
num = num + 1
i = i + 1
return num
return conts

这是一段我自己尝试的代码,系统会报错,信息显示i,num在没有被附值的情况下被调用了。现在这个问题还在各大论坛上有争论,但为例保证代码的流畅以及避免bug,在写代码时还是自动默认一个环境中只能调用自己以及全局幻境中的变量,其余的不要随意进行调用。

hw1

Q2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from operator import add, sub

def a_plus_abs_b(a, b):
"""Return a+abs(b), but without calling abs.

>>> a_plus_abs_b(2, 3)
5
>>> a_plus_abs_b(2, -3)
5
>>> # a check that you didn't change the return statement!
>>> import inspect, re
>>> re.findall(r'^\s*(return .*)', inspect.getsource(a_plus_abs_b), re.M)
['return h(a, b)']
"""
if b >= 0:
h = _____
else:
h = _____
return h(a, b)

非常有意思的一道题目,它其实展现的是python关于附值也就是=这一功能的强大之处,不但可以把名字赋给数值,还可以把名字赋给函数,答案如下:

1
2
3
4
5
6

if b >= 0:
h = add
else:
h = sub
return h(a, b)

Q5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def if_function(condition, true_result, false_result):
"""Return true_result if condition is a true value, and
false_result otherwise.

>>> if_function(True, 2, 3)
2
>>> if_function(False, 2, 3)
3
>>> if_function(3==2, 3+2, 3-2)
1
>>> if_function(3>2, 3+2, 3-2)
5
"""
if condition:
return true_result
else:
return false_result

def with_if_statement():
"""
>>> result = with_if_statement()
6
>>> print(result)
None
"""
if c():
return t()
else:
return f()

def with_if_function():
"""
>>> result = with_if_function()
5
6
>>> print(result)
None
"""
return if_function(c(), t(), f())

def c():
"*** YOUR CODE HERE ***"

def t():
"*** YOUR CODE HERE ***"

def f():
"*** YOUR CODE HERE ***"

很有趣的题目答案如下:

1
2
3
4
5
6
7
8
def c():
return False

def t():
print(5)

def f():
print(6)

这道题的问题在于为什么with_if_function在引用的时候会出现两个数字?问题的关键在于()with_if_function中返还if_function时该函数在干什么?它运行了三个函数c(),t(),f(),计算机会首先运行这三个函数并将这三个函数的返回。还记得上一章的纯函数吗?print只会在屏幕上打印出值,而返还None,所以我们运行t(),f()它们自然而然的在屏幕上打印出了5以及6。我们将代码改变一下
1
2
3
4
5
6
7
8
9
def with_if_function():
"""
>>> result = with_if_function()
5
6
>>> print(result)
None
"""
return if_function(c(), t, f)

这时你会发现with_if_function只出现一个数字了。我们在这里有必要补充一下函数的调用(call)

补充:函数调用

函数的定义如下def name(parameter1,parameter2...): body函数有一个名字,有相应的参数,以及函数的主体。当你需要一个函数的时候呼叫它的名字即可,调用函数名,由return返回相应的值,则一次完整的函数调用就完成了,但是请注意namename(parameater1...)二者不是一个东西,前者是使用函数名对函数进行调用,即将函数作为参数或者返回值(在之后的heigher order function中会有涉及),在终端中输入一个函数的名称返回回来的是function<..>括号中跟着函数寄存的内存地址,而后者则是直接使用或是运行这个函数,代表了该函数的返回值。综上在Q5中我们运行了函数ft它们的返回值None作为了参数输入了if_function中在运行ftprint函数发挥了威力,它在函数运行时在屏幕上打印出了相应值。

Lab01

Q2

WWDP。本问比较多暂且不写回答了,最主要的问题是python对于False的判定,除了False本身的字符串会被判定为“假”意外,0,[],None会被判定为“假”,注意负数,小数在判定中都认为是“真”的,只有0才被判定为假。
此外and以及or的返回值要特别注意,它们会返回最后计算的数值and在计算到有一个为假后会返还那个为假命题的数值并跳过后面所有的计算,注意是数值而不是Booleans(True、False),or则是在计算到有一个为真后就会返还该数值并结束计算。在此处需要注意的是Error具有最高等级,只要任何被判定项中出现错误,则会自动结束并返回错误类型例如:

1
2
3
4
>>>1/0 or 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

Q8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def double_eights(n):
"""Return true if n has two eights in a row.
>>> double_eights(8)
False
>>> double_eights(88)
True
>>> double_eights(2882)
True
>>> double_eights(880088)
True
>>> double_eights(12345)
False
>>> double_eights(80808080)
False
"""
"*** YOUR CODE HERE ***"

有很多类型重复的题目,在此就先拿出一道来讲,答案如下:

1
2
3
4
5
6
7
8
while n > 1:
if n % 10 == 8:
n = n//10
if n % 10 ==8:
return True
else:
n = n // 10
return False

唯一需要注意的点就是//整除符号的运用,在使用整除符号后会忽略小数,是用来解决针对输入数字的每一位单独数字进行处理的必要工具。

雅可比矩阵与雅可比行列式

本文着重探讨雅可比行列式以及雅可比矩阵在基础力学(张量分析)中的一些作用,以及作用的证明,方便之后的流体力学、连续介质力学等基础力学的推导(并不包括代数几何方面的内容)

定义与概念

我们定义

上式中右侧矩阵称为雅可比矩阵,其中$x_i=x_i(\xi_1,\xi_2,\cdots,\xi_n)(i=1,2,3\cdots m)$解释为任意一个x都是关于$\xi$的函数。而雅可比矩阵的行列式就是雅可比行列式。在此处我举一个不太严谨的例子来帮助读者理解,我们都知道要表达一个一元函数在某一点处的线性逼近值,我们有它在这一点上的导数,而当我们将一元函数的概念扩大到n维空间向量函数向m维空间的另一个向量函数的映射时,它的导数就是我们的主角————雅可比矩阵。我们用具体的实例来阐释它。

雅可比矩阵在坐标变换中的使用

雅可比矩阵在张量中最重要的作用就是坐标转化。取笛卡尔坐标系(记为$D$)下的一组基矢量$x^{k’}$将其记作$x^{1’},x^{2’},x^{3’}$,同时取任意坐标系(记为$K$)一组基矢量$x^i$,将其记为$x^1,x^2,x^3$,二者以角标上撇号区分,相对的每一个笛卡尔坐标基矢量都是零一坐标系下三个基矢量的函数,反之亦然,用公式表示为:

当满足雅可比行列不为零时即:

就可以得到雅可比矩阵,并用来坐标转化。现有一向量在$D$下表达为$\overrightarrow{V}=(v_1’,v_2’,v_3’)=v_k’$,欲求得该向量在$K$下表示我们可以直接将其与雅可比矩阵相乘即

以上是两个三维坐标间的相互转化,拓展到任意维度之间的坐标转化,雅可比矩阵适用方法不变。

雅可比行列式在n维空间中微元体积转化作用及其证明

再来看雅可比行列式的作用,我们现在推导n维空间中不同坐标系下的体积转化。
设我们在坐标系$A_1$中有基矢量$y_i$其中$(i=1,2,3\cdots n)$,而在坐标系$A_2$中有基矢量$x_j$其中$(j=1,2,3 \cdots n)$自然在坐标系$A_1$中就有单位体积元为$dy_1dy_2\cdots dy_n$那么该体积在坐标系$A_2$中到底是如何表示的呢?
先给出结论:

其中$|\frac{\partial y_i}{\partial x_j}|$指代雅可比行列式的绝对值。[1]
很遗憾,在全网搜寻n维空间中单位元有关雅可比行列式的严格证明时遇见了困难,大多数数学资料只是将证明推证到二维(三维)便戛然而止,网络上现成的n维空间证明在严格推证后我个人认为有一定问题,暂不列于本文,但从美国数学协会网站以及各种教科书中都明确表示我们的结论是正确的且被广泛运用的。
在此我们证明二维及三维中雅可比行列式在单位元转化时的作用,并把他们表达在最常用的多重积分中。(以下证明整理自[1])
首先是二重积分:

注意此处我们将绝对值符号去掉了,并在等号左边加上了正负号,代表不知道雅可比行列式的最终值是正还是负。
我们做出几个假设:
1.上式中的$R_{xy}$和$R_{uv}$是在xy、uv平面上的闭合区域,其边界$C_{xy}$以及$C_{uv}$分段光滑
2.被积函数$F(x,y)$在相应区间内可导且一次导数连续
3.等式右边的$f(u,v)$及$g(u,v)$在相应区域内有连续的二阶导,且只要在$D_{uv}$上就在相应的$R_{xy}$上
4.点$(u,v)$在$xy$平面上有对应映射点$(x,y)$,且$x=f(u,v),y=g(u,v)$
我们根据此创造了很宽松的假设条件,除了$R_{uv}$边界上的点在$R_{xy}$的边界上需要一一映射,其余区域内部的点并没有一一映射的要求,也就是说只要在$R_{uv}$内部的点在$R_{xy}$上有映射就可以。
我们令$Q(x,y)$满足

则原式为(反向运用Green公式)

我们再将$x,y$用$f,g$表示,将$dy$用$dg$的全微分形式表示则有:

如果你对Green公式足够熟悉,则你应该有感觉,经过一系列变换我们的式子已经迫近Green公式了。我们再做一次变换,可以看得更清楚。
令$P_1=Q\frac{\partial g}{\partial u},Q_1=Q\frac{\partial g}{\partial v}$原式变为

进行格林公式变换!

让我们尝试着把它们展开回到$x,y$表达看看会发什么!
原式为

其中$\frac{\partial Q}{\partial x}=F[f(u,v),g(u,v)]$而最最重要的是,后面的部分就是雅可比行列式

综上:

这里雅可比行列式用于连接二维空间中不同的微元,我们推广到n维空间则有我们一开始给出的结论,在这里再次复述一遍:

其中$|\frac{\partial y_i}{\partial x_j}|$指代雅可比行列式的绝对值。

注意这里的体积转化涉及两个问题,一是由于微元体积大小不存在负数,雅可比行列式必须要取绝对值,二是这里的转化不能再像雅可比矩阵中任意维度之间随意转化,而是必须在同一维度下转化。
(未完待续)

参考文献

[1]《Advanced Calculus by Wilfred Kaplan》 fifth edition page:94

摘要

加州伯克利大学自2011年起启用了python作为CS61a这一课程的主要编程语言,课程内容也与之前相比有了不少变化(主题内容以及中心思想并无改变),更重要的是课程资源有了极大的丰富,而网络上很少有中文版本的对相应资源的使用指导,这让我在开始学习时也遇到了不少问题;而CS61a作为编程开山课程,做到了让高中生也可以学习本课程,但是让他们克服如此障碍有些困难,于是我决定将一些问题总结汇总,希望能够帮助中文母语的编程初学者。希望你玩得愉快!!!

在终端中操作样例的常见错误

在伯克利大学2020年春季的SICP版本中,课后的script并没有仔细说明在终端中操作python的具体步骤,导致如果没有看课堂视频的同学们迟迟无法进行操作,现将其归结如下:
凡是看到代码第一行中出现>>>代表了该代码是在终端中运行的,请windows的同学启用git bash并在其中输入python,使得其转入python并显示>>>而不是原来的~$

大家看到的script中的换行好像在git bash中很难实现,其实只要正确的输入相应的指令,例如

>>>from opeartor import mul
>>>def square(x):
       return mul(x, x) 

但如果你正确输入了相关指令,再摁下回车键后,你会发现git bash中出现的不是>>>,而是...,如下图:

现在我们再来回看几个在开始编写程序时容易出现的问题:
1.注意是def不是define!!!与我们在编写scheme时是完全不一样的。
2.注意def后要加:,且一定是英文的。
3.注意缩进,python因为没有像scheme中的括号以及c或c++中的;所以它的各种语句辨识都靠缩进,一般来讲一个函数在跳入下一级定义时需要缩进,也就是常说的Tab键,是四个空格。

SICP提交作业方法

python版SICP最大的改进就是它的自动审批作业系统,我们在CS61a官网上按照课程表下载相应的lab与hw文件,不用联网就可以自动检测自己的作业以及lab是否正确,该系统叫做ok。现在介绍windows系统下的ok系统的具体使用方法。
请在电脑中预先安装Git,有关Git的介绍在另一篇博客上。在安装选项时除了图示选项

其余选项都可以使用默认。

hw的批改方法

每一次的hw作业都会在课程网站上有一个相应的压缩文件可供下载,建议建立一个属于CS61a的文件夹方便管理。在相应网站上都会有一行相应的命令用于作业检查(忽略作业提交指令,只有拥有伯克利大学账号的同学才可以提交)我们以hw01为例,如图
进入课程主页找到hw01

进入后可以下载相应压缩文件并解压

文件中包含我们的作业代码(忽略其他,只关注我们的作业代码即可),运用编辑器打开作业代码并根据网站上的作业要求完成作业,此时我们回到网站,我们会发现在每一道题的后面都会有检查指令,可是该检查指令该怎样使用呢?

回到我们储存作业的文件夹,鼠标右键,你会发现选项中多了Git Bash here这个选项(注意!!!一定是要在该文件夹内部鼠标右键!!!

我们使用从网站上拷贝下来的指令后加--local输入进git bash中就会启动ok程序对我们写的代码进行批改。注意,在命令的最后添加local的意思是将所写代码仅在本地进行校验,而不将其上传,如果忘记添加则系统会要求输入邮箱,十分麻烦。

而如果错误系统也会显示相应的错误类型,方便纠错。

总结

提交相应某一问题时只需要在相应位置打开git bash输入指令python ok -q <name> --local其中<>中的内容需要在课程网站上查询,或者在代码中查看,每一道题都有相应的名称,如果想要一次性全部验证只需要输入python ok即可(由于python版本的差别,该指令的第一个单词可以是python python3 py此项依据python版本而定)

lab的批改方法

lab共分为两个部分,课堂部分以及课下练习,其中课下联系的部分与hw相同,需要首先在lab中下载压缩文件,打开压缩文件中的lab01.py(借用第一次实验为例)根据注释中的相关要求,完成代码,并借助代码中的题目名称进行ok校验即可。
lab线上课程的操作方法则较为简单在相应的文件夹中git bash here,根据网站上直接输入网站上的指令就可以开始答题


?处输入相应的答案即可。

(新补充)关于check import error

在后续作业中会有部分题目中存在字符串检查的doctest,它在启动ok程序后会调用同文件夹下的另一个代码文件construct_check.py在极个别情况下,下载作业压缩文件时会导致该文件为空或者丢失,此时在ok检查时无论如何都会有错误import error,解决方法仅需要将相关文件补齐即可。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178

from ast import parse, NodeVisitor, Name

_NAMES = {
'Add': '+',
'And': 'and',
'Assert': 'assert',
'Assign': '=',
'AugAssign': 'op=',
'BitAnd': '&',
'BitOr': '|',
'BitXor': '^',
'Break': 'break',
'Recursion': 'recursive call',
'ClassDef': 'class',
'Continue': 'continue',
'Del': 'del',
'Delete': 'delete',
'Dict': '{...}',
'DictComp': '{...}',
'Div': '/',
'Ellipsis': '...',
'Eq': '==',
'ExceptHandler': 'except',
'ExtSlice': '[::]',
'FloorDiv': '//',
'For': 'for',
'FunctionDef': 'def',
'GeneratorExp': '(... for ...)',
'Global': 'global',
'Gt': '>',
'GtE': '>=',
'If': 'if',
'IfExp': '...if...else...',
'Import': 'import',
'ImportFrom': 'from ... import ...',
'In': 'in',
'Index': '...[...]',
'Invert': '~',
'Is': 'is',
'IsNot': 'is not ',
'LShift': '<<',
'Lambda': 'lambda',
'List': '[...]',
'ListComp': '[...for...]',
'Lt': '<',
'LtE': '<=',
'Mod': '%',
'Mult': '*',
'Nonlocal': 'nonlocal',
'Not': 'not',
'NotEq': '!=',
'NotIn': 'not in',
'Or': 'or',
'Pass': 'pass',
'Pow': '**',
'RShift': '>>',
'Raise': 'raise',
'Return': 'return',
'Set': '{ ... } (set)',
'SetComp': '{ ... for ... } (set)',
'Slice': '[ : ]',
'Starred': '',
'Sub': '-',
'Subscript': '[]',
'Try': 'try',
'Tuple': '(... , ... )',
'UAdd': '+',
'USub': '-',
'While': 'while',
'With': 'with',
'Yield': 'yield',
'YieldFrom': 'yield from',
}

def check(source_file, checked_funcs, disallow, source=None):
"""Checks that AST nodes whose type names are present in DISALLOW
(an object supporting 'in') are not present in the function(s) named
CHECKED_FUNCS in SOURCE. By default, SOURCE is the contents of the
file SOURCE_FILE. CHECKED_FUNCS is either a string (indicating a single
name) or an object of some other type that supports 'in'. CHECKED_FUNCS
may contain __main__ to indicate an entire module. Prints reports of
each prohibited node and returns True iff none are found.
See ast.__dir__() for AST type names. The special node name 'Recursion'
checks for overtly recursive calls (i.e., calls of the form NAME(...) where
NAME is an enclosing def."""
return ExclusionChecker(disallow).check(source_file, checked_funcs, source)

class ExclusionChecker(NodeVisitor):
"""An AST visitor that checks that certain constructs are excluded from
parts of a program. ExclusionChecker(EXC) checks that AST node types
whose names are in the sequence or set EXC are not present. Its check
method visits nodes in a given function of a source file checking that the
indicated node types are not used."""

def __init__(self, disallow=()):
"""DISALLOW is the initial default list of disallowed
node-type names."""
self._disallow = set(disallow)
self._checking = False
self._errs = 0

def generic_visit(self, node):
if self._checking and type(node).__name__ in self._disallow:
self._report(node)
super().generic_visit(node)

def visit_Module(self, node):
if "__main__" in self._checked_funcs:
self._checking = True
self._checked_name = self._source_file
super().generic_visit(node)

def visit_Call(self, node):
if 'Recursion' in self._disallow and \
type(node.func) is Name and \
node.func.id in self._func_nest:
self._report(node, "should not be recursive")
self.generic_visit(node)

def visit_FunctionDef(self, node):
self._func_nest.append(node.name)
if self._checking:
self.generic_visit(node)
elif node.name in self._checked_funcs:
self._checked_name = "Function " + node.name
checking0 = self._checking
self._checking = True
super().generic_visit(node)
self._checking = checking0
self._func_nest.pop()

def _report(self, node, msg=None):
node_name = _NAMES.get(type(node).__name__, type(node).__name__)
if msg is None:
msg = "should not contain '{}'".format(node_name)
print("{} {}".format(self._checked_name, msg))
self._errs += 1

def errors(self):
"""Returns the number of number of prohibited constructs found in
the last call to check."""
return self._errs

def check(self, source_file, checked_funcs, disallow=None, source=None):
"""Checks that AST nodes whose type names are present in DISALLOW
(an object supporting the contains test) are not present in
the function(s) named CHECKED_FUNCS in SOURCE. By default, SOURCE
is the contents of the file SOURCE_FILE. DISALLOW defaults to the
argument given to the constructor (and resets that value if it is
present). CHECKED_FUNCS is either a string (indicating a single
name) or an object of some other type that supports 'in'.
CHECKED_FUNCS may contain __main__ to indicate an entire module.
Prints reports of each prohibited node and returns True iff none
are found.
See ast.__dir__() for AST type names. The special node name
'Recursion' checks for overtly recursive calls (i.e., calls of the
form NAME(...) where NAME is an enclosing def."""

self._checking = False
self._source_file = source_file
self._func_nest = []
if type(checked_funcs) is str:
self._checked_funcs = { checked_funcs }
else:
self._checked_funcs = set(checked_funcs)
if disallow is not None:
self._disallow = set(disallow)
if source is None:
with open(source_file) as inp:
p = parse(open(source_file).read(), source_file)
else:
p = parse(source, source_file)
self._errs = 0

self.visit(p)
return self._errs == 0

纯函数以及非纯函数

纯函数

在python终端中调用abs函数

>>> abs(-2)
2

我们说abs是一纯函数,它有一个input返回一个output,只要input相等,output一定是相等的。

非纯函数

python中的函数print是一个典型的非纯函数,我们在终端输入

>>> print(1,2,3)
1 2 3

你可能会开始纳闷,只要输入1,2,3它返回的不应该都是相同的结果吗?为什么它不是纯函数呢?想到这里可以说明你已经掌握了纯函数的奥义了,但请不要急,接着往下看。
请尝试以下输入

>>>print(print(1),print(2))
1
2
None None

你会发现,结果与你预想之中的不一样,多了两个None,我们现在来揭晓答案,print函数不论接收任何输入,其返回值都是None,该值在python中表示无,没有。而你的屏幕上显示的东西是print函数的功能:即在屏幕上打印出所输入的值。但是请十二万分的注意,它只是把东西打印在了屏幕上,其本身什么数值都没有返还!所以它是一个彻头彻尾的非纯函数。
上述例子中,括号内第二个和第三个print只是老老实实地把你交给它的数字打了出来而已,而第一个print也只是老老实实的把收到的两个print返回的None打印了出来而已。
我们再来看一个例子

>>>two = print(2)
2
>>>print(two)
None %有些人会报错

第一个式子中print接收到2,于是老老实实的把2打印了出来,而第二个例子中因为two没有给print赋予任何的数值,print会返回None,而新版python的interpreter会把two当成没有定义的函数,从而报错。

参考资料

本文基于加州伯克利大学CS61a2020年春季公开资料写成,目的在于帮助他人更好的使用相关资源,以上仅为本人经验总结及学习回顾,详细资料请自行访问加州伯克利大学CS61a课程官网https://inst.eecs.berkeley.edu/~cs61a/archives.html,如果对ok有什么问题请查阅https://cal-cs-61a-staff.github.io/ok-help/

前言与背景介绍

SICP是著作Structure and Interpreatation of Computer Programs的简称,同时是北美一众CS强校(例如加州伯克利,滑铁卢大学等)的CS第一门专业课Functional Programming(下简称FP)的教材。笔者抱着兴趣与学习计算机技术与一窥人类计算机教育最前沿的目的,在有一定计算机基础的情况下接触了这门课(学习资源为SICP、加州大学伯克利分校于YouTube上上传的网课CS61A、以及该课程任课教授在互联网上完全公开的教案、教学顺序表),只是初探就受益匪浅,完完全全刷新了本人对计算机及编程教育的认知,相比国内的计算机教育,北美的计算机教育显然显得更为成熟,而且对待有无计算机基础的同学并无差别,这是我们非常值得学习的。
从内容讨论这门课程,课程的中心在于编程 而不是像通常我所熟知的国内计算机教育顺序中心在于 编程语言。相比较于国内一上来就用C语言作为学生的“开山语言”,FP则使用了不用关心内存分配,不用担心抬头引用的高级语言Scheme,引用课程中教授的原话并做翻译:“我们学习这门课是让你们对计算机有一个大的框架,让你们知道编程到底是什么,剩下的问题例如内存分配,那是你们在学会编程之后的细节问题。”关联我当时学习计算机的过程,在学习C和C++时,我们更关注的好像确实不是编程本身,而是C++的各种已经封装好的工具,就像知道继承多态的好像就要比只知道函数的同学要上一个档次,知道STL的同学就要比知识到达继承多态的同学要牛逼一些。
本课程的目的在于使用最简单的工具(if-else函数与condition函数)将我们日后所熟知的大部分功能函数以及数据结构实现,扪心自问一下,自己是否真的学会的“递归”这一重要方法,如果只有一个if-else函数,你能否写出while函数?能否完成线性表?树?图?作为自以为是的学生,我本人需要反思,同时我们的计算机教育也需要反省并承认自己的不足及落后了。这一系列文章相当于是在学习该门课程中的各种总结,以及有些在书中课里资料里都找不到的问题答案,我也会从stack overflow中翻译整理下来,为技术打破语言壁垒尽一份力。
我在写相关系列文章时会尽量将一些重点名词用原文代入,因为本人计算机水平过浅,随意翻译可能会导致信息偏差。同样本人计算机水平十分有限,有错误之处在所难免,还请见谅。
附CS61A课程网站:https://inst.eecs.berkeley.edu//~cs61a/su10/index.html课程教案、讨论内容、作业包括课本都在该网站上免费向全世界提供。
特别致谢CS61A任课教授、http://teachyourselfcs.com网站所有、以及在stack overflow、CSDN、知乎等平台为scheme这门冷门计算机语言答疑解惑,为学习SICP的学生们指路的前辈。

Scheme安装与开始步骤(2021年)

由于网上较多流传的网课版本为2010年的CS61A录像,课堂上教授使用的编辑器与编译器在十年后的今天有了更优选择,请自行搜索并于官网下载DrRackethttps://racket-lang.org/,下载并使用exe安装完成后请于安装处打开文件夹,双击打开,看到所示页面点击package manager,在这里我们有两种选择,都同样的可以满足本课程的全部所需,见下图点击下载即可。进入DrRacket后你会发现界面分为两个部分,上面的部分是我们需要输入代码的区域,而下半部分则是输出反馈。
请记住,在之后的每一次编程中我们都需要在编程开始时选择语言,即在第一行输入# lang simply-scheme或者# lang sicp(基于个人安装),想要执行程序点击右上角Run即可。注意,当我们选择语言并且至少成功执行了一次后就会发现在我们的输出区域出现如下字样:若无,则有可能出现编译错误或者自己在写程序时忘记添加语言选择。

scheme基础语法

基础运算以及编写规范

scheme的基础语法以operator(操作符) 与 operands(受操作对象)组成,在scheme中将二者联系起来的符号是括号(paretheses)和空格,我们以最简单的加法为例

# lang simply-scheme
(+ 1 2)

以此为例,+是我们的operator,而1、2则是operands,scheme提供前置算符运算,即所要进行的操作规则前置,要进行该项操作的对象之间用空格隔开,并与相应操作规则用同一个括号括起来。不光是加法,所有的基本运算以及之后我们要学习到的一些定义运算都符合这个规则,如果说在scheme语法中需要注意的事项,那么我认为只有一个:括号。浏览stack overflow,绝大多数初学者在编译出现问题时都是忘记在某一个不显眼的地方忘记了括号。scheme为nesting(嵌套)的运算提供了比较简便的输入方法例如:

# lang simply-scheme
(* (+ 1 2) (+ 3 4) (+ 5 6))

但这么些会出现一些问题,就像我们之前提到的括号是scheme中最重要的元素,写成一行会让代码可读性很差,为此我们规定一些代码编写规则,在嵌套运算时,长元素之间用换行分割,而运算归属(或者是先后)则用缩进来表示,以上式为例

# lang simply-scheme
(* (+ 1 2)
   (+ 3 4)
   (+ 5 6)
)

式中并列的三个元素代表它们地位相等,在缩进中都在*之后,代表他们都是该操作符的元素。在scheme中(在大多数情况下都代表着某一个操作的开始,在写代码时请不断自我审问,我要开始某一个操作指令了吗?我加括号了吗?
编程中还有一个重要的内容,comment,scheme的comment符号为;在此之后的所有内容计算机不会编译。
引用书中原话,在编程的过程中我们只处理两种东西,procedure和data,从计算机的角度出发二者在计算机内的存储处理方式其实都是数据,只不过一个是我们要操作的对象,另一个则是我们进行操作的方法。接下来我们看一种特别的操作方式。

define

我们可以用该命令对某个数据附值,例如:

# lang simply-scheme
(define a 2)

意为将2的值赋予名称a,之后我们可以使用a来代替2进行一系列代数运算,在输入端执行改代码后,在输入端输入(其中>为输入命令,无该符号则是电脑输出结果)

>a
2
>(+ a 1)
3

然而define的强大之处不止于此,我们看下面的一个例子

# lang simply-scheme
(define (square x) (* x x))

运行之后在输出端输入

>(square 10)
100
>(squre 3)
9

根据我们的输入代码以及相关的测试结果,我们不难得到,我们使用definesquare定义为了函数,而函数表达式为随函数后跟的参数的平方,在此先给出define在定义函数时的用法

(define (函数名称 参数formal_parameter) (函数主体body));参数可以不为一,函数主体中为相应的表达式

相应的我们在调用已经定义的参数时遵守相应规则,(函数名称 参数)。值得一提的是,当我们在写函数主体时可以不拘束于基本的运算,函数主体也可以包含函数,还是用书上的原例子,接续上面的函数继续,(注意以上样例公式中为了表达简便并没有按照代码编写规范,在如下实例中我们运用代码表达规范对define进行规范表达,请一定养成这样的习惯。)

(define (sum_square x y) 
        (+ 
            (square x) 
            (square y)
        )
)

此例中针对一个函数我们有两个参数,且在函数主体中调用了之前存在的函数进行运算。
我们借由CS61A中教案的例子,加深对define命令的认识
思考

(define x 3)

(define (x) 3)

有什么差别?根据我们上面的所学知识,(define x 3)的目的是将数值3赋予x,而(define (x) 3)则完全不一样,它的意思是创造一个函数函数的名称为x,而这个函数就是3。我们分别运行以上两个函数,然后在输入端输入以下指令

>(x)

第一个函数报错,第二个函数正常得出结果。

>x+3

第一个函数正常运行,而第二个函数报错。这是因为我们最后得到的两个x他们的数据结构是不同的!!!请谨记这一点。

写在之前

鉴于本人微积分遗忘过多,这个就当作是微积分的重新复习,以及一些以前微积分学习过程中并没有注意到的基础知识,日积月累望有所突破。文本是基于重读参考文献[1]后的写作,其中有大量借鉴,总归来讲算是个人学习笔记,但基于本人个人情况做了一些调整,做到尽量将某一个简单的问题叙述明白,讲清楚。

三角函数基础

弧度及角度问题

弧度和角度都是用于描述一个角的大小的度量单位,类似于描述长度的单位有英寸厘米,二者从量纲角度来讲等价

三角函数名称由来

complementary 意为“互余”,故我们统共有6个三角函数,共四组:sin-cos,tan-cot,sec-csc。每一对函数之间有这样的关系

中文名中以“余”开头则是一组中的co函数(注:以上公式对co函数同样适用)
tan函数在$\frac{(2k+1)\pi}{2}\quad k\in Z$时无定义。上述中有两个三角函数sec csc较为陌生,$sec=\frac{1}{cos}$ $csc=\frac{1}{sin}$,与$sin-cos$这一对类似,$sec-csc$这一对形状相同,但一奇一偶,其中sec(中文名为正割)为偶函数,csc(中文名为余割)为奇函数,二者函数图像如下(图源来自Wikimedia)


如图,二者分别在$\frac{(2k+1)\pi}{2}\quad k\in Z$以及$k\pi\quad k\in Z$上无定义。
在这里引述一张非常有趣的图片来解释六个三角函数间的关系:

在这个六边形中,对角线两个函数的乘积为1(互为倒数),对于每一个阴影三角形,三角形的上端两角的值的平方和等于三角形下端角的平方,而每一个函数都等于其顺时针两函数之比,同样的任意一点的函数都等于这个函数所在左右端点的乘积。

三角函数导数与相关极限

几个重要极限

我们有一个基本的极限(或者说无穷小也可以),那就是

接下来我们来证明它,首先在单位圆上做相关的面积图有

设我们所研究的角度的弧度为x,其大于零但小于$\frac{\pi}{2}$,我们需要三个面积:以单位1为底,$\sin x$为高的三角形面积为:$\frac{\sin x}{2}$,弧度为x半径长度为1的扇形面积为:$\pi\times\frac{x}{2\pi}=\frac{x}{2}$,还有以$\tan x$为高,1为底的三角形面积为:$\frac{\tan x}{2}$。同时我们从图中可以清楚的得到以上三者的面积大小关系,将其代数化则有:

化简一下有:

每一项都乘以$\sin x$则有

而根据夹逼定理有当x从正向趋近于零时,目标函数有

同时由于函数$\frac{\sin x}{x}$为偶函数,所以其从正向或是负向逼近零的时候结果相同,证明完毕。
我们由它再推出几个重要极限:

拥有了以上几个重要极限之后,我们就可以对三角函数的导数进行推导了。
首先就是sin的导数推导,由导数定义式有:

同理运用以上的极限我们可以求得函数cos的导数:

以以上我们求得的这一组函数的导数为基础,我们可以运用链式规则,运用求导商法则,求出剩余四个三角函数的导数,在此不再一一证明,将其列举如下:

写出来后我们会发现一些规律,co函数最终的求导结果都带有负号,且与同一组函数的导数呈现互余。

反三角函数的导数及推导

我们直接给出反三角函数的导数及其中一部分的推导。我们令$y=arctan x$,现要求$\frac{dy}{dx}$

又因为$sec^2 y= 1 + x^2$综上有

$arctan$的值域是$[-\frac{\pi}{2},\frac{\pi}{2}]$而其定义域是$R$,为奇函数。同理我们推导$y = arcsec x$

$arcsec$的函数图像较为特殊,既不为奇函数也不为偶函数,我们在确定该函数定义域的时候应该从$sec$$[0,\pi]$的区间内提取出来,故其定义域为$(-\infty, -1]\cup[1,\infty)$
最后我们总结所有的反三角函数的导数值:

每一组之间的三角函数反三角函数的导数互为相反数。

连续性与可导性的备忘录

在第一次接触连续性与可导性时理解完全不深刻,所谓的一些可导必连续,连续不一定可导还是借助同学间流传的自行车的笑话记住的,本次对其进行简单但相对细致的探究,以求理解。

从导数的角度出发

在定义某一点的导数的时候我们有

从几何上讲(我们单纯的把它限制在一次函数内),这就是在曲线上先取一定点A$(x,f(x))$,然后在同一曲线上取除了该定点的另一点B$(x+h,f(x+h))$,用直线将这两点连接起来当h趋于零时,该直线的斜率就是导数,即就是该点切线的斜率。

再来思考连续

连续的定义为:$x \in I$(I为函数定义域)当$c-\delta < x < c+\delta$时有任意的正实数$\varepsilon$满足$f(c)-\varepsilon< f(x) < f(x)+\varepsilon$则该函数在c处连续。简单的讲,就是在左右逼近极限后,它所得到的是一个统一的值。现在给出一个例子,观察函数$y=|x|$,你会发现这个函数在定义域上是完全连续的,那么它同样在定义域内可导吗?
我们很容易的的发现,在$x=0$处,该函数图像是一个尖点,考虑之前我们对函数导数下的几何方面的定义,你会发现,函数在该点上没有唯一的切线,故其不可导。综上,连续是不一定可导的,我们接下来证明可导一定连续。
借由刚才得到的求导的定义式我们有:

综合之前连续性定义,有可导一定连续。

参考资料

[1]《普林斯顿微积分读本》 Adrian Banner著 杨爽等译
[2]Wikimedia官网https://commons.wikimedia.org/wiki/Main_Page
[3]tikz模板官网https://texample.net/media/tikz/examples/PDF/trigonometric-hexagon.pdf

什么是Git,什么又是Github?

先说Git

与众人所熟知的github不一样,git是一个软件,就如同你装在计算机里运行的所有其他软件一样,这是一个伟大的发明创造。

起源、定义

git的创造者是Linus Benedict Torvalds, 林纳斯先生的另一伟大创造就是Linux系统,而我们的主角Git也与Linux有着很深的联系,Git是为Linux专门打造的版本控制器。
综上我们现在有两个问题,什么是版本控制器?为什么Linux需要这个玩意?

先来解决第一个文题:什么是版本控制器?

版本控制器统称VCS(version control system)顾名思义,它就是用来控制版本的。如果你有玩过单机版RPG游戏,那么你一定会懂,在每次面对强大的boss时,你首先需要做的就是存档,当boss战对自己不利,或是消耗太多时,为了避免死亡,你可以选择读档,从之前你的存档处开始,既避免了死亡的惩罚,又不用像之前一样解决小怪。
版本控制器就是文件版的存档,当你在自己的计算机上完成某一个项目时,你可以借助VCS来实现版本的储存迭代,它会记录下每一次的版本内容,提交时间,你可以选择从现在跳回(Git中称为回滚)之前你储存的任何一个版本。
要做到以上,通常我们会这么做
这样会导致文件乱七八糟,如果起名不规则,还会导致文件错乱。但如果运用Git,文件夹中只会有一个文件,但却保存所有的文件版本。以上过程都在你自己的本地计算机中进行,叫做local VCS,Git当然能做到,但它做到的不仅于此。

我们先解决第二个问题:为什么Linux需要Git?

我在此对Linux不多作介绍,读者若对此没有概念,那请 一定 学习有关于它的相关资料,Linux是开源的操作系统,既然是开源的就会有杰出的程序员、黑客为它做出贡献,但是这就涉及到一个问题,我们该如何将这些代码片段合成来实现Linux的更新呢?原来的方法,是由林纳斯手动填补,但这样效率低下,重复劳动大,自2002年起,Linux开始使用Bitkeeper(一个商用软件)作为版本控制器,来实现版本更新,这是怎么实现的?我们借此机会介绍版本控制器的另外两种模式:Centralized VCS 和 Distributed VCS。
Centralize VCS由一个数据中心和多个开发者构成,所有的数据、文件等都存储且仅存储在数据中心中,开发者负责对数据中心中的文件进行创建debug等。这样的好处是管理数据中心者具有绝对的权限,他可以指定每一个开发者的权限,设定他能看到多少内容。但这样做却冒着巨大的风险,一旦数据中心遭到攻击或是出现系统故障,所有人的工作都有在一瞬间化泡影的风险。Distributed VCS填补了这样的问题。
它将原本的Local VCS与Centralized VCS相结合,它在每个人的计算机包括中心数据库上都建立了数据库,存放了自初始到最终版本中每一个文件版本,每个人都可以将自己的工作发布上传到中心数据库,看到文件更新的人都可以从数据中心下载保存在自己的计算机之中,在此基础上再做贡献。
Git就是帮助我们实现这一切的软件,而我们使用Git上传到的数据中心,就是大名鼎鼎的Github。
回到那段历史,Bitkeeper一直为Linux提供服务,直到2005年,该公司与Linux社区一众黑客决裂,相传原因是有人尝试对Bitkeeper进行破解(就是把人家的正版软件盗版化。顺带一提,Bitkeeper一直无偿为Linux社区提供服务。)没办法,社区们的诸位以林纳斯先生为首,于2005年推出了Git,来帮助Linux进行版本更新,代码补充。今天,Git不再单单是Linux的专属软件,它为windows、Linux、Mac等系统提供 无偿的、开源的服务,Git一经推出迅速占领了VCS的市场,随着Github的面世,Git的用户数量再次获得了爆炸性的增长。2016年,原本作为商业软件的Bitkeeper,宣布对外开源。

再来看看Github

就像我们之前提到过的,GitHub相对于Git而言是其数据中心,当然,基于git而开发的数据中心不止GitHub一个,比较知名的还有gitlab等。但是在所有为git提供服务的数据中心之中,github无疑是最大最成功的一个。截至2020年一月,GitHub已经拥有超过四千万的用户以及超过一亿九千万个代码仓库[4],这个数字还在不断的累加增长,GitHub毫无疑问是世界上最大的代码社区。继承了开源运动的优秀思想,GitHub的大多数服务都是免费的,读者现在看到的这个网站也是基于GitHub的服务器搭建起来的。笔者在此解决一些有关于GitHub最经常被问到的问题。

我们能用GitHub做什么?

尽管已经讲了那么多,作为一个开发者/学生/教师/科研人员,我们到底能用它做些什么?
就像我们刚才讲的一样,GitHub最原始的目的就是作为git的网络仓库,储存、分发下载、上传的基地,我们可以将原本存储在自己电脑上的版本信息上传至GitHub防止丢失,同时也可以供全世界的人一同下载一起开发。基于此,笔者再此给出GitHub一些常用用法。

持续性的在不同的电脑终端上进行自己的工作

假设我们在写一个项目,项目并未完成,因为外界原因我们需要转移阵地,为了能在其他地方继续我们的工作,我们应该怎么做?通常做法是使用U盘将自己需要继续的工作拷贝下来,换台电脑继续。GitHub则提供了另一种方案,我们可以将半完成品上传到GitHub中,到了另一个地方,打开终端从GitHub中将自己的工作下载然后继续coding。

创建小组工作

在开始这一段之前我们必须弄明白GitHub中的角色分配问题,GitHub中有organization(组织)、team(团队)以及个体三种单位,要想团队协作开发一个项目,我们首先需要创建一个组织,一个组织之中可以有多个项目,多个团队,而具体到一个团队之中就会有不同的分工存在。
一个团队之中有三类成员:admin(管理员)、write(编写者)、read(游客)
这三者各自所拥有的权限不一样,其中admin(管理员)拥有最多的权限包括:给项目添加成员、从代码仓库中复制代码到本地、将本地代码上传到仓库、在线阅读代码,而编写者只拥有后三项,其不能给项目添加成员,游客只能阅读和下载,而不能进行提交合并代码。
读到这里你可能已经发现了,好像不论是谁都可以下载阅读你写的代码,而你也可以阅读下载别人的代码。是的,GitHub提供免费服务的前提条件之一就是你所做的项目,你所写的代码要符合开源协定,就是你的代码可以被任意阅读下载,而如果你需要一个私密项目,抱歉,GitHub是要收费的。同时GitHub在组织某些方面的服务(例如大规模的组织云存储空间)也是有付费要求的,详情如下

为开源事业添砖加瓦

在GitHub上有众多全球范围内的开源合作项目,你可以选择加入,为其添砖加瓦,你所需要做的如下:

观察到每个开源项目的右上角都会有一个fork选项,选中后该仓库中的所有代码会自动移存到你的仓库中,你可以通过对你自己仓库中的版本进行修改添加(注意是对你自己的库中的版本,你是没有权限直接修改人家的东西的)修改完毕后,你可以选择向项目的原作者申请代码合并(pull request)简称PR,原作可以通过自己的github账号审阅你的代码,考虑是否将你的贡献添加入原来的代码中,一旦通过,你就是该项目的贡献者了。

参考资料:

[1]bitkeeper官网: https://www.bitkeeper.org/
[2]Git官网:https://git-scm.com/
[3]Progit written by Scott Chacon and Ben Straub(该书可以免费在Git官网上下载,文中思维导图均引自此书)
[4]https://en.wikipedia.org/wiki/GitHub