python与numpy库的学习总结¶
Python作为通用编程语言,凭借其丰富的科学计算库(如NumPy、SciPy)已成为数据科学的首选工具。其中NumPy通过ndarray对象和向量化运算,实现了媲美C语言的数值计算效率。
python 的基本语法¶
# 1. 打印输出 (Python 的 "Hello World")
print("欢迎来到Python世界!🐍")
# 2. 变量与数据类型
name = "小明" # 字符串
age = 20 # 整数
height = 1.75 # 浮点数
is_student = True # 布尔值
# 3. 字符串格式化 (f-string)
print(f"{name}今年{age}岁,身高{height}米")
# 4. 列表 (list) 操作
hobbies = ["编程", "阅读", "羽毛球"]
hobbies.append("音乐") # 添加元素
print(f"{name}的爱好: {hobbies}")
# 5. 条件判断
if age >= 18:
print("已成年")
else:
print("未成年")
# 6. 循环遍历
print("爱好列表:")
for index, hobby in enumerate(hobbies, 1):
print(f"{index}. {hobby}")
# 7. 函数定义
def calculate_circle_area(radius):
"""计算圆面积 (文档字符串说明)"""
pi = 3.14159
return pi * radius ** 2
# 8. 调用函数
r = 5
area = calculate_circle_area(r)
print(f"半径为{r}的圆面积: {area:.2f}") # 保留两位小数
# 9. 字典 (key-value 映射)
person = {
"姓名": name,
"年龄": age,
"职业": "学生"
}
print("\n个人信息字典:")
for key, value in person.items(): #person.items() 用于获取字典中所有键值对,返回类似(键, 值)的元组
print(f"{key}: {value}")
# 10. 列表推导式 (简洁创建列表)
squares = [x**2 for x in range(1, 6)]
print(f"\n平方数列表: {squares}")
欢迎来到Python世界!🐍 小明今年20岁,身高1.75米 小明的爱好: ['编程', '阅读', '羽毛球', '音乐'] 已成年 爱好列表: 1. 编程 2. 阅读 3. 羽毛球 4. 音乐 半径为5的圆面积: 78.54 个人信息字典: 姓名: 小明 年龄: 20 职业: 学生 平方数列表: [1, 4, 9, 16, 25]
同时支持面向对象和函数式编程¶
面向对象的编程 class¶
class GameCharacter:
# 构造函数:创建角色时初始化
def __init__(self, name, health=100):
self.name = name
self.health = health
self.is_alive = True
# 方法:角色行为
def take_damage(self, damage):
self.health -= damage
if self.health <= 0:
self.is_alive = False
print(f"{self.name}被击败了!")
else:
print(f"{self.name}受到{damage}点伤害,剩余生命值:{self.health}")
def heal(self, amount):
if self.is_alive:
self.health += amount
print(f"{self.name}恢复了{amount}点生命值,当前生命值:{self.health}")
else:
print(f"{self.name}已经死亡,无法治疗")
# 使用类创建角色实例
hero = GameCharacter("勇者亚瑟") # 我们使用类GameCharacter创建了一个名为勇者亚瑟,血量100的对象,现在其用hero表示
enemy = GameCharacter("邪恶巫师", 80) # 我们使用类GameCharacter创建了一个名为邪恶巫师,血量80的对象,现在其用enemy表示
# 角色交互
#可以使用hero.heal 等函数来调用类里的函数,以下是示例
hero.take_damage(30) # 勇者亚瑟受到30点伤害,剩余生命值:70
enemy.take_damage(90) # 邪恶巫师被击败了!
hero.heal(20) # 勇者亚瑟恢复了20点生命值,当前生命值:90
enemy.heal(10) # 邪恶巫师已经死亡,无法治疗
勇者亚瑟受到30点伤害,剩余生命值:70 邪恶巫师被击败了! 勇者亚瑟恢复了20点生命值,当前生命值:90 邪恶巫师已经死亡,无法治疗
从上述代码可以看出:
主体差异
- 类(面向对象):操作主体是对象本身(如
hero.take_damage(30)) - 函数式:操作主体是独立函数(如
take_damage(hero, 30))
- 类(面向对象):操作主体是对象本身(如
状态管理本质区别 ✅
- 类的核心优势:对象内部封装状态(属性),所有方法自动共享这些属性
# 类方法直接访问self属性 def take_damage(self, damage): self.health -= damage # 无需额外传递health参数
- 函数式痛点:每次操作都需显式传递所有相关状态
# 函数必须接收完整状态 def take_damage(character, damage): new_health = character["health"] - damage return {**character, "health": new_health} # 需返回完整新状态
- 类的核心优势:对象内部封装状态(属性),所有方法自动共享这些属性
状态变更方式 🔄
- 类:就地修改对象状态(可变性)
hero.health = 70 # 直接修改原对象
- 函数式:创建副本返回新状态(不可变性)
hero = take_damage(hero, 30) # 用新对象替换旧对象
- 类:就地修改对象状态(可变性)
认知模型差异 🧠
- 类:模拟现实实体("角色受伤" =
角色.受伤()) - 函数式:数据处理流程("应用伤害函数到角色数据")
- 类:模拟现实实体("角色受伤" =
函数式的编程¶
# 角色状态用字典表示
def create_character(name, health=100):
return {
"name": name,
"health": health,
"is_alive": True
}
# 操作函数:返回新状态而不是修改原状态
def take_damage(character, damage):
new_health = character["health"] - damage
is_alive = new_health > 0
print(f"{character['name']}受到{damage}点伤害,剩余生命值:{new_health if is_alive else 0}")
if not is_alive:
print(f"{character['name']}被击败了!")
return {
"name": character["name"],
"health": max(0, new_health),
"is_alive": is_alive
}
def heal(character, amount):
if not character["is_alive"]:
print(f"{character['name']}已经死亡,无法治疗")
return character
new_health = character["health"] + amount
print(f"{character['name']}恢复了{amount}点生命值,当前生命值:{new_health}")
return {
**character,
"health": new_health
}
# 创建角色
hero = create_character("勇者亚瑟")
enemy = create_character("邪恶巫师", 80)
# 角色交互(每次返回新状态)
hero = take_damage(hero, 30) # 勇者亚瑟受到30点伤害,剩余生命值:70
enemy = take_damage(enemy, 90) # 邪恶巫师被击败了!
hero = heal(hero, 20) # 勇者亚瑟恢复了20点生命值,当前生命值:90
enemy = heal(enemy, 10) # 邪恶巫师已经死亡,无法治疗
勇者亚瑟受到30点伤害,剩余生命值:70 邪恶巫师受到90点伤害,剩余生命值:0 邪恶巫师被击败了! 勇者亚瑟恢复了20点生命值,当前生命值:90 邪恶巫师已经死亡,无法治疗
认识NumPy的核心——ndarray¶
ndarray 是 NumPy 库中最核心的数据结构,全称是 N-dimensional array(N 维数组)。
简单来说,它是一个可以存储 多维数据 的容器,这里的 "N" 表示维度的数量:
- 当 N=1 时,就是一维数组(类似普通的数字列表)
- 当 N=2 时,就是二维数组(类似表格、矩阵)
- 当 N=3 时,就是三维数组(可以想象成多个表格叠在一起)
- 更高维度的数组(如 4D、5D 等)则用于更复杂的数据结构
它的特点:¶
- 同构性:里面所有元素必须是同一类型(比如全是整数或全是浮点数)
- 固定大小:创建后不能随意改变大小(这和 Python 列表不同)
- 连续内存存储:数据在内存中是连续存放的,这让运算效率更高
可以说,NumPy 的几乎所有功能都是围绕 ndarray 展开的,它是处理数值计算的基础。
import numpy as np #导入numpy库并将其使用缩写np表示
# 创建数组的多种姿势
python_list = [1, 2, 3] # 慢速的"杂货铺"
np_array = np.array([1, 2, 3]) # 高效的"集装箱"
print("Python列表内存:", python_list.__sizeof__()) # 72字节(含包装盒)
print("NumPy数组内存:", np_array.nbytes) # 24字节(纯货物)
Python列表内存: 72 NumPy数组内存: 24
import time
# 生成大数据
py_list = list(range(1000000))
np_arr = np.arange(1000000)
# 测试Python列表运算时间
start = time.time()
py_result = [x * 2 for x in py_list]
print("Python列表耗时:", time.time() - start)
# 测试NumPy数组运算时间
start = time.time()
np_result = np_arr * 2
print("NumPy数组耗时:", time.time() - start)
Python列表耗时: 0.05706977844238281 NumPy数组耗时: 0.002991199493408203
NumPy 运算快的核心原因是:
数据在内存中是连续存储的(像排队站整齐),而 Python 列表的元素可能分散在内存各处。
可以直接对整块数据进行操作(底层用 C 语言实现),更因为它深度利用了 CPU 的 SIMD 硬件加速技术,让一条指令同时 “干活”,这是 Python 原生列表(只能逐个处理元素)无法比拟的优势。
假设我们要对两个数组做加法:
a = np.array([1, 2, 3]) # 形状(3,)
b = np.array([[4], [5], [6]]) # 形状(3,1)
print(a + b)
print("a的形状:",a.shape)
print("b的形状:",b.shape)
[[5 6 7] [6 7 8] [7 8 9]] a的形状: (3,) b的形状: (3, 1)
直观来看,a是1行3列,b是3行1列,形状不同。但广播机制会自动处理这种情况,这就是广播的作用——让不同形状的数组“兼容”并进行运算。
广播的核心规则¶
广播遵循一套严格的规则,判断两个数组是否可以进行运算,以及如何自动扩展形状:
规则1:维度对齐¶
从形状的末尾(最右侧)开始,依次比较两个数组的维度大小:
- 如果维度大小相等,直接兼容
- 如果一个数组的维度大小为
1,会被“拉伸”(复制)到与另一个数组的对应维度大小一致 - 如果维度大小既不相等也不是
1,则无法广播,会报错
规则2:维度补全¶
如果两个数组的维度数量不同,维度少的数组会在前面(左侧)自动补1,直到两者维度数量相同。
举例说明:
- 数组
a形状(3,)→ 补全后(1, 3) - 数组
b形状(3,1)→ 维度数量已匹配,无需补全 - 补全后两者形状分别为
(1,3)和(3,1),满足广播条件
广播的具体过程(以上面的a + b为例)¶
- 补全维度:
a从(3,)补全为(1, 3),b保持(3, 1) - 维度拉伸:
a的第0维大小为1,拉伸到3(与b的第0维一致),变成(3, 3)[[1, 2, 3], [1, 2, 3], [1, 2, 3]]b的第1维大小为1,拉伸到3(与a的第1维一致),变成(3, 3)[[4, 4, 4], [5, 5, 5], [6, 6, 6]]
- 执行运算:对应位置元素相加,得到最终结果
常见广播案例¶
案例1:标量与数组运算¶
标量可以看作形状为()的数组,会被广播到与数组同形状:
a = np.array([1, 2, 3])
print(a + 5) # 等价于 [1+5, 2+5, 3+5] → [6,7,8]
[6 7 8]
#### 案例2:不同维度数组运算
a = np.ones((2, 3)) # 形状(2,3)
b = np.array([[4], [5]]) # 形状(2,1)
print(b)
print("--------")
print(a + b)
[[4] [5]] -------- [[5. 5. 5.] [6. 6. 6.]]
如果两个数组的对应维度既不相等也不是1,会报错:
### 广播不成立的情况
a = np.ones((2, 3)) # 形状(2,3)
b = np.ones((2, 4)) # 形状(2,4)
print(a + b) # 报错:无法广播,最后一维3≠4
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[9], line 4 2 a = np.ones((2, 3)) # 形状(2,3) 3 b = np.ones((2, 4)) # 形状(2,4) ----> 4 print(a + b) # 报错:无法广播,最后一维3≠4 ValueError: operands could not be broadcast together with shapes (2,3) (2,4)
广播的优势¶
- 节省内存:无需手动复制数据扩展数组(广播是“虚拟”扩展,实际不复制数据)
- 简化代码:避免写循环手动处理维度匹配
- 高效运算:底层仍利用SIMD指令加速,性能不受影响
理解广播机制是灵活使用NumPy的关键,它让数组运算更简洁、高效,尤其在处理高维数据(如图片、视频)时非常实用。
花式索引(Fancy Indexing)¶
一、什么是花式索引?¶
普通索引(如arr[0]、arr[1:3])只能按顺序或固定步长选取元素,而花式索引允许你通过索引数组(整数数组或布尔数组)来指定要选取的元素位置,实现 “跳跃式” 或 “条件式” 选取。
例如,从数组中同时选取第 0、2、4 个元素,或选取所有大于 10 的元素,都可以用花式索引实现。
arr = np.array([10, 20, 30, 40, 50])
indices = [0, 2, 4] # 要选取的元素下标
# 用整数数组索引
result = arr[indices]
print(result) # 输出:[10 30 50]
[10 30 50]
二、二维数组的花式索引¶
需要指定行索引和列索引(都是整数数组),两者长度必须相同,返回的是对应位置的元素组成的一维数组。
arr = np.array([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
])
# 行索引:选取第0行和第2行
# 列索引:对应行中选取第1列和第0列
rows = [0, 2]
cols = [1, 0]
result = arr[rows, cols]
print(result) # 输出:[2 7]
# 原理:(0,1) → 2,(2,0) → 7
[2 7]
# 选取第0行和第2行的所有列
print(arr[[0, 2], :])
# 输出:
# [[1 2 3]
# [7 8 9]]
[[1 2 3] [7 8 9]]
# 选取所有行的第0列和第2列
print(arr[:, [0, 2]])
# 输出:
# [[1 3]
# [4 6]
# [7 9]]
[[1 3] [4 6] [7 9]]
三、布尔数组索引(条件筛选)¶
通过一个布尔数组(与原数组形状相同)作为索引,选取所有True位置对应的元素,常用于按条件筛选元素。
- 一维数组的布尔索引
arr = np.array([1, 3, 5, 7, 9])
# 生成布尔数组(判断元素是否大于5)
mask = arr > 5
print(mask) # 输出:[False False False True True]
# 用布尔数组筛选元素
result = arr[mask]
print(result) # 输出:[7 9]
[False False False True True] [7 9]
可以直接在索引中写条件表达式,更简洁:
print(arr[arr > 5]) # 输出:[7 9]
[7 9]
- 二维数组的布尔索引
arr = np.array([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
])
# 筛选所有大于5的元素(返回一维数组)
print(arr[arr > 5]) # 输出:[6 7 8 9]
# 结合行/列条件(例如:第0行中大于2的元素)
row_mask = [True, False, False] # 只选第0行
col_mask = arr[0] > 2 # 第0行中大于2的元素位置:[False False True]
print(arr[row_mask, col_mask]) # 输出:[3]
[6 7 8 9] [3]
四、花式索引的特点¶
1.返回副本而非视图:与普通切片(返回视图,修改会影响原数组)不同,花式索引返回的是原数组的副本,修改结果不会改变原数组。
arr = np.array([10, 20, 30])
indices = [0, 1]
result = arr[indices]
result[0] = 100 # 修改副本
print(arr) # 原数组不变:[10 20 30]
[10 20 30]
2.支持多维索引组合:可以混合使用普通索引、切片和花式索引。
arr = np.arange(12).reshape(3, 4) # 3行4列数组
print(arr)
print('-------')
print(arr[[0, 2], 1:3]) # 第0、2行,第1-2列
# 输出:
# [[ 1 2]
# [ 9 10]]
[[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] ------- [[ 1 2] [ 9 10]]
3.索引数组可以是任意形状:只要行索引和列索引的形状匹配,结果会保持对应形状。
arr = np.array([10, 20, 30, 40])
indices = np.array([[0, 1], [2, 3]]) # 2x2的索引数组
print(arr[indices]) # 结果也是2x2:[[10 20], [30 40]]
[[10 20] [30 40]]
indices = np.array([[0, 1], [2, 3],[0,3],[1,2]]) # 4x2的索引数组
print(arr[indices]) # 结果也是 4x2
[[10 20] [30 40] [10 40] [20 30]]
花式索引是 NumPy 中非常灵活的索引方式,通过整数数组或布尔数组,可以轻松实现非连续、条件化的元素选取。它的核心是 “用数组作为索引”,相比循环筛选,代码更简洁,效率也更高(底层用 C 实现)。掌握花式索引能大幅提升数据处理的灵活性。
通用函数(ufunc)¶
NumPy 中的通用函数(ufunc,全称 Universal Functions) 是处理数组元素级运算的核心工具,它们能直接对数组中的每个元素执行操作,无需编写循环,既简化代码又提升效率。
1. 一元ufunc(对单个数组操作)¶
接收一个数组,返回一个同形状的新数组,常见的有:
| 函数名 | 功能说明 | 示例 |
|---|---|---|
np.sqrt |
计算每个元素的平方根 | np.sqrt([4, 9, 16]) → [2 3 4] |
np.square |
计算每个元素的平方 | np.square([2, 3]) → [4 9] |
np.exp |
计算每个元素的e^x | np.exp([0, 1]) → [1. 2.718...] |
np.log |
计算自然对数(ln) | np.log([1, e]) → [0. 1.] |
np.abs |
计算绝对值 | np.abs([-1, -2.5]) → [1. 2.5] |
np.sin/np.cos |
计算正弦/余弦值 | np.sin(np.pi/2) → 1.0 |
np.round |
四舍五入到指定小数位数 | np.round([2.345], 2) → [2.35] |
2. 二元ufunc(对两个数组操作)¶
接收两个数组,返回一个同形状的新数组(或通过广播兼容后的形状),常见的有:
| 函数名 | 功能说明 | 示例 |
|---|---|---|
np.add |
对应元素相加 | np.add([1,2], [3,4]) → [4 6] |
np.subtract |
对应元素相减 | np.subtract([5,6], [3,1]) → [2 5] |
np.multiply |
对应元素相乘 | np.multiply([2,3], [4,5]) → [8 15] |
np.divide |
对应元素相除 | np.divide([6,8], [2,4]) → [3. 2.] |
np.power |
第一个数组元素为底,第二个为指数 | np.power([2,3], [3,2]) → [8 9] |
np.maximum |
取对应位置的最大值 | np.maximum([3,1], [2,5]) → [3 5] |
np.minimum |
取对应位置的最小值 | np.minimum([3,1], [2,5]) → [2 1] |
3.运算符重载¶
NumPy中,Python的算术运算符(+、-、*等)其实是ufunc的“语法糖”,例如:
a + b等价于np.add(a, b)a * b等价于np.multiply(a, b)a **b等价于np.power(a, b)这让代码更符合直觉,例如:
a = np.array([1, 2])
b = np.array([3, 4])
print(a + b) # 等价于np.add(a, b) → [4 6
print(np.add(a, b))
[4 6] [4 6]
通过out参数可以指定运算结果的存储位置,避免额外创建数组,节省内存:
a = np.array([1, 2, 3])
result = np.empty(3) # 预先创建一个空数组
np.square(a, out=result) # 计算结果直接存入result
print(result) # [1. 4. 9.]
[1. 4. 9.]
很多ufunc提供reduce方法,用于对数组元素进行“累积运算”,例如:
np.add.reduce(a):计算数组所有元素的和(等价于np.sum(a))np.multiply.reduce(a):计算数组所有元素的乘积(等价于np.prod(a))
a = np.array([1, 2, 3, 4])
print(np.add.reduce(a)) # 1+2+3+4 → 10
print(np.multiply.reduce(a)) # 1×2×3×4 → 24
10 24
自定义ufunc(高级用法)
如果内置ufunc满足不了需求,可以用np.frompyfunc创建自定义ufunc:
# 定义一个普通Python函数(对单个元素操作)
def my_func(x, y):
return x * 2 + y
# 转换为ufunc(输入参数2个,输出1个)
my_ufunc = np.frompyfunc(my_func, 2, 1)
# 用自定义ufunc处理数组
a = np.array([1, 2, 3])
b = np.array([10, 20, 30])
print(my_ufunc(a, b)) # [12 24 36]
[12 24 36]
通用函数(ufunc)是NumPy处理元素级运算的核心,它通过C语言底层实现,结合广播机制,既简化了代码(无需循环),又大幅提升了运算效率。
掌握常用ufunc(如算术运算、数学函数)及其特性(运算符重载、聚合方法、广播支持),能让你更高效地处理NumPy数组,是数据分析和科学计算的基础技能。
爱因斯坦求和约定的函数介绍
np.einsum('abcde,edcba->ae', A, B)是使用爱因斯坦求和约定来执行一种特定的张量运算。以下是具体解释:
- 运算原理:在
'abcde,edcba->ae'这个字符串中,abcde是数组A的下标,edcba是数组B的下标,->ae表示输出数组的下标。根据爱因斯坦求和约定,重复出现的下标(b、c、d)表示要对这些维度进行求和操作,而未重复的下标(a和e)则会保留在输出数组中,作为输出数组的维度。 - 等价运算:该操作等价于先将数组
A和B按照对应元素相乘,然后在b、c、d这三个维度上进行求和。从数学角度看,对于给定的A和B,输出结果中的每个元素result[i, j]可通过以下公式计算: $$ result[i, j]=\sum_{m}\sum_{n}\sum_{p}A[i, m, n, p, j] \times B[j, p, n, m, i] $$ - 数据形状要求:数组
A和B必须是五维数组,且它们在b、c、d这三个维度上的大小要相同,A的第1维和B的第5维大小相同,A的第5维和B的第1维大小也相同,最终输出数组的形状由A的第1维和第5维大小决定。例如,如果A的形状是(2, 3, 4, 4, 5),那么B的形状必须是(5, 4, 4, 3, 2),输出数组的形状就是(2, 5)。
这种运算在一些涉及高维张量运算的场景,如深度学习中的张量操作、量子力学中的矩阵运算等方面有重要应用,可以简洁地实现复杂的张量收缩运算,避免编写复杂的循环代码。
# 计算两个5D张量的收缩积
A = np.random.rand(2,3,4,5,6)
B = np.random.rand(6,5,4,3,2)
result = np.einsum('abcde,edcba->ae', A, B) # 输出形状(a,e)->(2,6)
print(result)
[[16.1035043 15.10452065 15.63464197 13.73515194 14.76342649 16.40782354] [15.52701833 12.09717977 12.06971319 17.66764689 17.40100602 14.52402041]]
构建一个简单的量子计算模拟器¶
🔧 基础工具包¶
| 函数/模块 | 量子计算用途 | 数学对应 |
|-------------------------|---------------------------------------|---------------------------|
| np.array(..., dtype=complex) | 存储量子态(复数向量) | ℂⁿ向量空间 |
| np.kron() | 构建多量子比特系统(张量积) | ⊗运算 |
| np.eye() | 生成单位门/泡利I门 | 幺正矩阵 |
| np.matmul()或np.dot(v,v) | 态矢量归一化检查 | $ \langle \psi|\psi\rangle =1 $ |
# 单量子比特门库
X = np.array([[0, 1], [1, 0]]) # 量子NOT门(泡利X)
Y = np.array([[0, -1j], [1j, 0]]) # 泡利Y门
Z = np.array([[1, 0], [0, -1]]) # 相位翻转门
H = np.sqrt(0.5)*np.array([[1, 1], [1, -1]]) # Hadamard门
# 受控非门(CNOT) - 2量子比特
CNOT = np.array([
[1,0,0,0],
[0,1,0,0],
[0,0,0,1],
[0,0,1,0]
])
示例:贝尔态的制备
# 初始化两量子比特 |00⟩
psi = np.kron(np.array([1,0]), np.array([1,0]))
# 应用Hadamard门到第一个比特
H_on_q0 = np.kron(H, np.eye(2)) # 张量积扩展
psi = H_on_q0 @ psi # @是矩阵的乘法
# 应用CNOT门
psi = CNOT @ psi
print("贝尔态:", psi)
# 输出: [0.70710678 0 0 0.70710678] (即 (|00⟩+|11⟩)/√2
贝尔态: [0.70710678 0. 0. 0.70710678]
📊 测量模拟
def measure(psi, n_shots=1000):
# 计算概率分布
probs = np.abs(psi)**2
# 蒙特卡洛采样
results = np.random.choice(len(psi), p=probs, size=n_shots)
return np.bincount(results)/n_shots
print("贝尔态测量统计:", measure(psi))
贝尔态测量统计: [0.494 0. 0. 0.506]
np.kron() 函数¶
进行张量积运算
# 示例1:1D数组的克罗内克积
a = np.array([1, 2, 3])
b = np.array([4, 5])
print("1D数组的克罗内克积:")
print(np.kron(a, b)) # 输出:[ 4 5 8 10 12 15]
print("形状:", np.kron(a, b).shape) # 输出:(6,) (3*2)
# 示例2:2D矩阵的克罗内克积
A = np.array([[1, 2],
[3, 4]])
B = np.array([[0, 5],
[6, 7]])
print("\n2D矩阵的克罗内克积:")
print(np.kron(A, B))
# 输出:
# [[ 0 5 0 10]
# [ 6 7 12 14]
# [ 0 15 0 20]
# [18 21 24 28]]
print("形状:", np.kron(A, B).shape) # 输出:(4, 4) (2*2, 2*2)
# 示例3:高维数组的克罗内克积
C = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) # 形状(2,2,2)
D = np.array([[9, 10], [11, 12]]) # 形状(2,2)
print("\n高维数组的克罗内克积形状:", np.kron(C, D).shape) # 输出:(2, 4, 4) (2, 2*2, 2*2)
1D数组的克罗内克积: [ 4 5 8 10 12 15] 形状: (6,) 2D矩阵的克罗内克积: [[ 0 5 0 10] [ 6 7 12 14] [ 0 15 0 20] [18 21 24 28]] 形状: (4, 4) 高维数组的克罗内克积形状: (2, 4, 4)
对于两个矩阵(2D 数组)的情况:¶
设 $ A $ 是一个形状为 $ (m, n) $ 的矩阵,$ B $ 是一个形状为 $ (p, q) $ 的矩阵,则它们的克罗内克积 $ \text{kron}(A, B) $ 是一个形状为 $ (m \times p, n \times q) $ 的矩阵,其数学定义为:
$$ \text{kron}(A, B) \;=\; \begin{bmatrix} A_{11}B & A_{12}B & \cdots & A_{1n}B \\ A_{21}B & A_{22}B & \cdots & A_{2n}B \\ \vdots & \vdots & \ddots & \vdots \\ A_{m1}B & A_{m2}B & \cdots & A_{mn}B \\ \end{bmatrix} $$
其中:
- $ A_{ij} $ 表示矩阵 $ A $ 中第 $ i $ 行、第 $ j $ 列的元素;
- $ A_{ij}B $ 表示用标量 $ A_{ij} $ 乘以矩阵 $ B $(即对 $ B $ 的所有元素都乘以 $ A_{ij} $);
- 整个结果矩阵由这些缩放后的 $ B $ 按 $ A $ 的元素排列方式组合而成。
对于高维数组(维度 $ \geq 3 $)的情况:¶
克罗内克积的定义可以自然扩展到高维数组。设 $ A $ 是形状为 $ (a_1, a_2, \dots, a_k) $ 的 $ k $ 维数组,$ B $ 是形状为 $ (b_1, b_2, \dots, b_k) $ 的 $ k $ 维数组(维度数需相同),则它们的克罗内克积 $ \text{kron}(A, B) $ 是一个形状为 $ (a_1 \times b_1, a_2 \times b_2, \dots, a_k \times b_k) $ 的 $ k $ 维数组。
其元素定义为: $$ \text{kron}(A, B)_{i_1 \times b_1 + j_1,\; i_2 \times b_2 + j_2,\; \dots,\; i_k \times b_k + j_k} \;=\; A_{i_1, i_2, \dots, i_k} \times B_{j_1, j_2, \dots, j_k} $$
其中:
- $ i_1 \in [1, a_1],\; i_2 \in [1, a_2],\; \dots,\; i_k \in [1, a_k] $(遍历 $ A $ 的所有索引);
- $ j_1 \in [1, b_1],\; j_2 \in [1, b_2],\; \dots,\; j_k \in [1, b_k] $(遍历 $ B $ 的所有索引);
- 结果数组的索引通过原数组索引与对应维度大小的乘积组合而成。
关键性质总结:¶
- 非交换性:$ \text{kron}(A, B) \neq \text{kron}(B, A) $(除非 $ A $ 或 $ B $ 是标量)。
- 结合性:$ \text{kron}(\text{kron}(A, B), C) = \text{kron}(A, \text{kron}(B, C)) $。
- 与矩阵乘法的关系:若 $ A、B、C、D $ 是维度兼容的矩阵,则 $ \text{kron}(A, B) \cdot \text{kron}(C, D) = \text{kron}(A \cdot C, B \cdot D) $。
这些公式化定义严格描述了 np.kron() 的运算逻辑,无论是低维还是高维数组,其核心都是“元素缩放 + 结构重组”。
# 2维数组与1维数组的克罗内克积
A = np.array([[1, 2], [3, 4]]) # 形状(2, 2)
B = np.array([5, 6]) # 形状(2,)(1维)
# 计算克罗内克积
result = np.kron(A, B)
print("结果形状:", result.shape) # 输出 (4, 2)
print(result)
结果形状: (2, 4) [[ 5 6 10 12] [15 18 20 24]]
# 示例:1维数组与2维数组的克罗内克积
A = np.array([1, 2]) # 形状(2,)(1维数组)
B = np.array([[3, 4], [5, 6]])# 形状(2, 2)(2维数组)
# 计算克罗内克积
result = np.kron(A, B)
print("A的形状:", A.shape) # (2,)
print("B的形状:", B.shape) # (2, 2)
print("结果形状:", result.shape) # 输出 (2, 4)
print("结果:\n", result)
A的形状: (2,) B的形状: (2, 2) 结果形状: (2, 4) 结果: [[ 3 4 6 8] [ 5 6 10 12]]
np.eye(n) 生成n维的单位门¶
print(np.eye(3))
[[1. 0. 0.] [0. 1. 0.] [0. 0. 1.]]
np.dot(v.conj(),v) 内积¶
# 1. 定义一个归一化的量子态(例如:|0>态)
psi_normalized = np.array([1/np.sqrt(2), 1j/np.sqrt(2)], dtype=np.complex128)
print("归一化量子态 psi_normalized =", psi_normalized)
# 使用np.dot计算内积(<psi|psi>)
inner_product_dot = np.dot(psi_normalized.conj(), psi_normalized)
print("使用np.dot计算内积 <psi|psi> =", inner_product_dot) # 应输出 1.0
# 使用np.matmul计算内积(需将行向量与列向量相乘)
# 将向量转为行向量(1x2)和列向量(2x1)
row_vec = psi_normalized.conj().reshape(1, -1)
col_vec = psi_normalized.reshape(-1, 1)
inner_product_matmul = np.matmul(row_vec, col_vec)[0, 0]
print("使用np.matmul计算内积 <psi|psi> =", inner_product_matmul) # 应输出 1.0
inner_product_at = (row_vec @ col_vec)[0, 0] # @替代np.matmul函数调用
print("使用@计算内积 <psi|psi> =", inner_product_at)
# 2. 定义一个非归一化的量子态
psi = np.array([1, 1j], dtype=np.complex128)
print("\n非归一化量子态 psi =", psi)
# 计算当前内积(应为2)
current_norm_sq = np.dot(psi.conj(), psi)
print("当前内积 <psi|psi> =", current_norm_sq)
# 计算归一化因子(模长)
norm = np.sqrt(current_norm_sq)
print("态的模长 =", norm)
# 归一化操作:除以模长
psi_normalized = psi / norm
print("归一化后的态 =", psi_normalized)
# 验证归一化结果
print("归一化后内积 <psi|psi> =", np.dot(psi_normalized.conj(), psi_normalized)) # 应输出 1.0
归一化量子态 psi_normalized = [0.70710678+0.j 0. +0.70710678j] 使用np.dot计算内积 <psi|psi> = (0.9999999999999998+0j) 使用np.matmul计算内积 <psi|psi> = (0.9999999999999998+0j) 使用@计算内积 <psi|psi> = (0.9999999999999998+0j) 非归一化量子态 psi = [1.+0.j 0.+1.j] 当前内积 <psi|psi> = (2+0j) 态的模长 = (1.4142135623730951+0j) 归一化后的态 = [0.70710678+0.j 0. +0.70710678j] 归一化后内积 <psi|psi> = (0.9999999999999998+0j)
对于量子态而言,我们可以使用向量来表示量子态,然后使用np.matmul()或 np.dot()或者 @ 来计算内积,确保态的归一性。