C_Meng PSNA

Never wait for the storm to pass, just dance in the rain.

0%

写给自己:

  1. 不管别人有没有催,不管有没有投稿日,都要给自己一个deadline
  2. 想法不成熟也没关系,方法有瑕疵也没关系,总之先开始写,把想到的东西都写下来
  3. 整理出第一版,就算再烂,也比没有强
  4. 拿着写好内容,再去找各路大神讨论,不断优化,事半功倍

图例

说明

关系名称 说明 图形
泛化(Generalization) 是一种继承关系, 表示一般与特殊的关系, 它指定了子类如何特化父类的所有特征和行为. 带三角箭头的实线,箭头指向父类
实现(Realization) 是一种类与接口的关系, 表示类是接口所有特征和行为的实现. 带三角箭头的虚线,箭头指向接口
关联(Association) 是一种拥有的关系, 它使一个类知道另一个类的属性和方法 带普通箭头(或实心三角形箭头)的实心线,指向被拥有者
聚合(Aggregation) 是整体与部分的关系, 且部分可以离开整体而单独存在 带空心菱形的实心线,菱形指向整体
组合(Composition) 是整体与部分的关系, 但部分不能离开整体而单独存在 带实心菱形的实线,菱形指向整体
依赖(Dependency) 是一种使用的关系, 即一个类的实现需要另一个类的协助 带箭头的虚线,指向被使用者

多目标优化是多准则决策的一个领域,它是涉及多个目标函数同时优化的数学问题。多目标优化已经应用于许多科学领域,包括工程、经济和物流,其中需要在两个或多个相互冲突的目标之间进行权衡的情况下作出最优决策。分别涉及两个和三个目标的多目标优化问题的例子有:在购买汽车时降低成本,同时使舒适性最大化;在使车辆的燃料消耗和污染物排放最小化的同时将性能最大化。在实际问题中,甚至可以有三多个目标。

对于非平凡多目标优化问题,不存在同时优化每个目标的单个解决方案。在这种情况下,目标函数被说成是冲突的,并且存在一个(可能无限)数量的帕累托最优解。如果目标函数在值上不能改进而不降低其他一些目标值,则解决方案称为非支配、Pareto最优、Pareto有效或非劣解。如果没有额外的主观偏好信息,所有Pareto最优解都被认为是同样好的(因为向量不能完全排序)。研究人员从不同的角度研究多目标优化问题,从而在设置和解决多目标优化问题时存在不同的求解哲学和目标。目标可以是找到帕累托最优解的代表性集合,and/or量化满足不同目标的折衷,and/or找到满足人类决策者decision maker(DM)的主观偏好的单一解决方案。

形式化定义

$$
\begin{array}{cl}{\min / \max } & {f_{m}(x), \quad m=1,2, \ldots, M} \\ {\text { subject to }} & {g_{j}(x) \geq 0, \quad j=1,2, \ldots, J} \\ {} & {h_{k}(x)=0, \quad k=1,2, \ldots, K} \\ {} & {x_{i}^{(Lower)} \leq x_{i} \leq x_{i}^{(Upper)}, \quad i=1,2, \ldots, n} \end{array}
$$

发展历史

年份 事件 相关论文
1906 Pareto提出核心思想 Pareto, V. (1906). Manuale di economia politica (Vol. 13). Societa Editrice.
1979 Stadler, W. 对帕累托最优进一步回顾 Stadler, W. (1979). A survey of multicriteria optimization or the vector maximum problem, part I: 1776–1960. Journal of Optimization Theory and Applications, 29(1), 1-52.
2008 Miettinen, K.提出一种混合方法解决多目标问题 Miettinen, K., Ruiz, F., & Wierzbicki, A. P. (2008). Introduction to multiobjective optimization: interactive approaches. In Multiobjective Optimization (pp. 27-57). Springer, Berlin, Heidelberg.
2014 Deb, K.对多目标优化进行回顾 Deb, K. (2014). Multi-objective optimization. In Search methodologies (pp. 403-449). Springer, Boston, MA.
2018 Sener, O., & Koltun, V.提出多任务学习来作为多目标优化的策略 Sener, O., & Koltun, V. (2018). Multi-task learning as multi-objective optimization. In Advances in Neural Information Processing Systems (pp. 524-535).

多目标优化算法归结起来有传统优化算法和智能优化算法两大类。

  1. 传统优化算法包括加权法、约束法和线性规划法等,实质上就是将多目标函数转化为单目标函数,通过采用单目标优化的方法达到对多目标函数的求解。
  2. 智能优化算法包括进化算法(Evolutionary Algorithm, 简称EA)、粒子群算法(Particle Swarm Optimization, PSO)等。

从九十年代初开始,进化算法系列算法被统一,如遗传算法等。

多目标优化问题的解

在单目标优化问题中,通常最优解只有一个,而且能用比较简单和常用的数学方法求出其最优解。然而在多目标优化问题中,各个目标之间相互制约,可能使得一个目标性能的改善往往是以损失其它目标性能为代价,不可能存在一个使所有目标性能都达到最优的解,所以对于多目标优化问题,其解通常是一个非劣解的集合——Pareto解集。

帕累托最优(Pareto Optimal):帕雷托最优是指资源分配的一种理想状态。给定固有的一群人和可分配的资源,如果从一种分配状态到另一种状态的变化中,在没有使任何人境况变坏的前提下,使得至少一个人变得更好,这就是帕雷托改善。帕雷托最优的状态就是不可能再有更多的帕雷托改善的状态;换句话说,不可能在不使任何其他人受损的情况下再改善某些人的境况。帕累托最优解集的边界(boundary)被称为帕累托最优前沿面(Pareto-optimal front)。

在存在多个Pareto最优解的情况下,如果没有关于问题的更多的信息,那么很难选择哪个解更可取,因此所有的Pareto最优解都可以被认为是同等重要的。由此可知,对于多目标优化问题,最重要的任务是找到尽可能多的关于该优化问题的Pareto最优解。因而,在多目标优化中主要完成以下两个任务:

  1. 找到一组尽可能接近Pareto最优域的解。
  2. 找到一组尽可能不同的解。

第一个任务是在任何优化工作中都必须的做到的,收敛不到接近真正Pareto最优解集的解是不可取的,只有当一组解收敛到接近真正Pareto最优解,才能确保该组解近似最优的这一特性。

几种常用的多目标优化问题解决方法

Weighted Sum Method

线性加权法,其中权重代表了每个目标函数的重要程度。

$$
\begin{array}{rl}{\min } & {F(x)=\sum_{m=1}^{M} w_{m} f_{m}(x)} \\ {\text { subject to }} & {g_{j}(x) \geq 0, \quad j=1,2, \ldots, J} \\ {} & {h_{k}(x)=0, \quad k=1,2, \ldots, K} \\ {} & {x_{i}^{(Lower)} \leq x_{i} \leq x_{i}^{(Upper)}, \quad i=1,2, \ldots, n}\end{array}
$$

优点:简单

缺点:很难设定一个权重向量能够去获得帕累托最优解;在一些非凸情况不能够保证获得帕累托最优解

$\varepsilon$ -constraint method

只保留一个目标函数,其他的目标函数被设定的值约束。

$$
\begin{array}{cl}{\min } & {f_{\mu}(x)} \\ {\text {subject to }} & {f_{m}(x) \leq \epsilon_{m}, \quad m=1,2, \ldots, M \text { and } m \neq \mu} \\ {} & {g_{j}(x) \geq 0, \quad j=1,2, \ldots, J} \\ {} & {h_{k}(x)=0,} \quad {k=1,2, \ldots, K} \\ {} & {x_{i}^{(Lower)} \leq x_{i} \leq x_{i}^{(Upper)},} \quad {i=1,2, \ldots, n}\end{array}
$$

优点:能够应用到凸函数和非凸函数场景下

缺点:函数需要精心选择;需要在独立目标函数的最小值或最大值之内

Weighted Metric Method

$$
\begin{array}{cl}{\min } & {l_{p}(x)=\left(\sum_{m=1}^{M} w_{m}\left|f_{m}(x)-z_{m}^{*}\right|\right)^{1 / p}} \\ {\text { subject to }} & {g_{j}(x) \geq 0, \quad j=1,2, \ldots, J} \\ {} & {h_{k}(x)=0, \quad k=1,2, \ldots, K} \\ {} & {x_{i}^{(Lower)} \leq x_{i} \leq x_{i}^{(Upper)}, \quad i=1,2, \ldots, n}\end{array}
$$

优点:weighted Techebycheff metirc能够保证获得所有帕累托最优解

缺点:需要有每个函数最大值和最小值的先验知识;需要每个目标函数的 $z^{*}$ 能够独立被找到;对于较小的p值,不一定保证所有能够获得所有帕累托最优解;随着p增加,问题会变得不可求导

Multi-Objective Genetic Algorithms

基于遗传算法的多目标优化就是利用遗传算法的原理来搜索帕累托最优前沿面。

遗传算法相比与传统算法的优点是能够得到一个最优解集,而不是单单一个最优解,这样给我们更多的选择。但计算复杂度可能稍高,而且里面涉及的一些函数需要精心设计。

详见:https://imonce.github.io/2019/11/07/启发式算法学习(三):遗传算法/

发展分析

多目标优化算法相对成熟,在不同的问题使用不同的优化算法。NSGA-II, SPEA 或者 MOPSO都是可选项,而到底选择哪一个方法,还需要根据特定的情景选择。

尽管多目标优化算法应用于动态的制造系统,dynamic manufacturing systems,但是制造系统和很复杂的,并且是动态的,因此算法还是有一定的缺陷性。

未来发展方向:

  1. 因为多种多目标方法已经被提出,混合方法 hybrid method可以被进一步发展。

  2. 现在的动态制造系统中的多目标优化算法需要动态调度的能力

Reference:
https://www.jiqizhixin.com/graph/technologies/cf8932be-519a-4fd9-84f9-c6ffa997a554
https://blog.csdn.net/paulfeng20171114/article/details/82454310
https://hpzhao.github.io/2018/09/17/多目标优化四种方法/
https://zh.wikipedia.org/wiki/帕累托最优

算法简介

蚁群算法(Ant Colony Algorithm, AG, or Ant Colony Optimization, ACO),又称蚂蚁算法,是一种用来在图中寻找优化路径的机率型算法。它由Marco Dorigo于1992年在他的博士论文“Ant system: optimization by a colony of cooperating agents”中提出,其灵感来源于蚂蚁在寻找食物过程中发现路径的行为。蚁群算法是一种模拟进化算法,初步的研究表明该算法具有许多优良的性质。针对PID控制器参数优化设计问题,将蚁群算法设计的结果与遗传算法设计的结果进行了比较,数值仿真结果表明,蚁群算法具有一种新的模拟进化优化方法的有效性和应用价值。

基本原理

蚂蚁会在其经过的路径上释放一种可以称之为“信息素”的物质,蚁群内的蚂蚁对“信息素”具有感知能力,它们会沿着“信息素”浓度较高路径行走,而每只路过的蚂蚁都会在路上留下“信息素”,这就形成一种类似正反馈的机制,这样经过一段时间后,整个蚁群就会沿着最短路径到达食物源了。可以分解为以下几步:

  1. 蚂蚁在路径上释放信息素。
  2. 碰到还没走过的路口,就随机挑选一条路走。同时,释放与路径长度有关的信息素。
  3. 信息素浓度与路径长度成反比。后来的蚂蚁再次碰到该路口时,就选择信息素浓度较高路径。
  4. 最优路径上的信息素浓度越来越大。
  5. 最终蚁群找到最优寻食路径。

算法流程

蚁群算法解决旅行商问题的流程:

数学模型

这个利用TSP问题来说明这个数学模型,对于TSP问题,设蚂蚁群体中蚂蚁的数量为m,城市的数量为n,城市i与城市j之间的距离为 $d_{ij}$ ,t时刻城市i与城市j连接路径上的信息素浓度为 $c_{ij}(t)$ 。初始时刻,蚂蚁被放置在不同的城市里,且各城市键连接路径上的信息素浓度相同。然后蚂蚁将按一定概率选择线路,不放设 $p^k_{ij}(t)$ 为t时刻蚂蚁k从城市i转移到城市j的概率。“蚂蚁TSP”策略收到两方面的左右,首先是访问某城市的期望,领完便是其他蚂蚁释放的信息素浓度。所以已定义:

$$p_{ij}^{k}(t)=\left\lbrace\begin{array}{cll}
\frac{[c_{ij}(t)]^{a} * [n_{ij}(t)]^{b}}{\sum [c_{ij}(t)]^{a} * [n_{ij}(t)]^{b}} & , & j \in allowk \\
0 & , & j \notin allowk
\end{array}\right.$$

其中:

  • $n_{ij}(t)$ 为启发函数,表示蚂蚁从城市i转移到城市j的期望
  • $allowk$ 为蚂蚁带访问城市集合,开始时, $allowk$ 中有 $n−1$ 个元素,即包括除了蚂蚁k出发城市的其他多个城市,随着时间的推移, $allowk$ 中的元素越来越少,直至为空
  • $a$ 为信息素重要程度因子
  • $b$ 为启发函数因子

在蚂蚁遍历各城市的过程中,与实际情况相似的是,在蚂蚁释放信息素的同事,各个城市之间连接路径上的信息素的强度也在通过挥发等方式逐渐消失。为了描述这个特征,设ρ表示信息素挥发程度。这样所有蚂蚁完成走完一遍所有城市之后,各个城市键连接路径上的信息素浓度为

$$c_{ij}(t+1) = (1-\rho)*c_{ij}(t) + \Delta c_{ij}$$

$$\Delta c_{ij} = \sum \Delta c^k_{ij}$$

$\Delta c^k_{ij}$ 为第k只蚂蚁在城市i与城市j连接路径上释放信息素而增加的信息素浓度。

$\Delta c_{ij}$ 为所有蚂蚁在城市i与城市j连接路径上释放信息素而增加的信息素浓度。

一般情况下 $\Delta c^k_{ij}=\frac{Q}{L_k}$ ,若蚂蚁k从城市i访问了城市j,其中 $Q$ 为信息素常数, $L_k$ 为第k只蚂蚁经过路径总长度。

python代码

代码来源:https://blog.csdn.net/fanxin_i/article/details/80380733

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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# -*- coding: utf-8 -*-
import random
import copy
import time
import sys
import math
import tkinter #//GUI模块
import threading
from functools import reduce


# 参数
'''
ALPHA:信息启发因子,值越大,则蚂蚁选择之前走过的路径可能性就越大
,值越小,则蚁群搜索范围就会减少,容易陷入局部最优
BETA:Beta值越大,蚁群越就容易选择局部较短路径,这时算法收敛速度会
加快,但是随机性不高,容易得到局部的相对最优
'''
(ALPHA, BETA, RHO, Q) = (1.0,2.0,0.5,100.0)
# 城市数,蚁群
(city_num, ant_num) = (50,50)
distance_x = [
178,272,176,171,650,499,267,703,408,437,491,74,532,
416,626,42,271,359,163,508,229,576,147,560,35,714,
757,517,64,314,675,690,391,628,87,240,705,699,258,
428,614,36,360,482,666,597,209,201,492,294]
distance_y = [
170,395,198,151,242,556,57,401,305,421,267,105,525,
381,244,330,395,169,141,380,153,442,528,329,232,48,
498,265,343,120,165,50,433,63,491,275,348,222,288,
490,213,524,244,114,104,552,70,425,227,331]
#城市距离和信息素
distance_graph = [ [0.0 for col in range(city_num)] for raw in range(city_num)]
pheromone_graph = [ [1.0 for col in range(city_num)] for raw in range(city_num)]



#----------- 蚂蚁 -----------
class Ant(object):

# 初始化
def __init__(self,ID):

self.ID = ID # ID
self.__clean_data() # 随机初始化出生点

# 初始数据
def __clean_data(self):

self.path = [] # 当前蚂蚁的路径
self.total_distance = 0.0 # 当前路径的总距离
self.move_count = 0 # 移动次数
self.current_city = -1 # 当前停留的城市
self.open_table_city = [True for i in range(city_num)] # 探索城市的状态

city_index = random.randint(0,city_num-1) # 随机初始出生点
self.current_city = city_index
self.path.append(city_index)
self.open_table_city[city_index] = False
self.move_count = 1

# 选择下一个城市
def __choice_next_city(self):

next_city = -1
select_citys_prob = [0.0 for i in range(city_num)] #存储去下个城市的概率
total_prob = 0.0

# 获取去下一个城市的概率
for i in range(city_num):
if self.open_table_city[i]:
try :
# 计算概率:与信息素浓度成正比,与距离成反比
select_citys_prob[i] = pow(pheromone_graph[self.current_city][i], ALPHA) * pow((1.0/distance_graph[self.current_city][i]), BETA)
total_prob += select_citys_prob[i]
except ZeroDivisionError as e:
print ('Ant ID: {ID}, current city: {current}, target city: {target}'.format(ID = self.ID, current = self.current_city, target = i))
sys.exit(1)

# 轮盘选择城市
if total_prob > 0.0:
# 产生一个随机概率,0.0-total_prob
temp_prob = random.uniform(0.0, total_prob)
for i in range(city_num):
if self.open_table_city[i]:
# 轮次相减
temp_prob -= select_citys_prob[i]
if temp_prob < 0.0:
next_city = i
break

# 未从概率产生,顺序选择一个未访问城市
# if next_city == -1:
# for i in range(city_num):
# if self.open_table_city[i]:
# next_city = i
# break

if (next_city == -1):
next_city = random.randint(0, city_num - 1)
while ((self.open_table_city[next_city]) == False): # if==False,说明已经遍历过了
next_city = random.randint(0, city_num - 1)

# 返回下一个城市序号
return next_city

# 计算路径总距离
def __cal_total_distance(self):

temp_distance = 0.0

for i in range(1, city_num):
start, end = self.path[i], self.path[i-1]
temp_distance += distance_graph[start][end]

# 回路
end = self.path[0]
temp_distance += distance_graph[start][end]
self.total_distance = temp_distance


# 移动操作
def __move(self, next_city):

self.path.append(next_city)
self.open_table_city[next_city] = False
self.total_distance += distance_graph[self.current_city][next_city]
self.current_city = next_city
self.move_count += 1

# 搜索路径
def search_path(self):

# 初始化数据
self.__clean_data()

# 搜素路径,遍历完所有城市为止
while self.move_count < city_num:
# 移动到下一个城市
next_city = self.__choice_next_city()
self.__move(next_city)

# 计算路径总长度
self.__cal_total_distance()

#----------- TSP问题 -----------

class TSP(object):

def __init__(self, root, width = 800, height = 600, n = city_num):

# 创建画布
self.root = root
self.width = width
self.height = height
# 城市数目初始化为city_num
self.n = n
# tkinter.Canvas
self.canvas = tkinter.Canvas(
root,
width = self.width,
height = self.height,
bg = "#EBEBEB", # 背景白色
xscrollincrement = 1,
yscrollincrement = 1
)
self.canvas.pack(expand = tkinter.YES, fill = tkinter.BOTH)
self.title("TSP蚁群算法(n:初始化 e:开始搜索 s:停止搜索 q:退出程序)")
self.__r = 5
self.__lock = threading.RLock() # 线程锁

self.__bindEvents()
self.new()

# 计算城市之间的距离
for i in range(city_num):
for j in range(city_num):
temp_distance = pow((distance_x[i] - distance_x[j]), 2) + pow((distance_y[i] - distance_y[j]), 2)
temp_distance = pow(temp_distance, 0.5)
distance_graph[i][j] =float(int(temp_distance + 0.5))

# 按键响应程序
def __bindEvents(self):

self.root.bind("q", self.quite) # 退出程序
self.root.bind("n", self.new) # 初始化
self.root.bind("e", self.search_path) # 开始搜索
self.root.bind("s", self.stop) # 停止搜索

# 更改标题
def title(self, s):

self.root.title(s)

# 初始化
def new(self, evt = None):

# 停止线程
self.__lock.acquire()
self.__running = False
self.__lock.release()

self.clear() # 清除信息
self.nodes = [] # 节点坐标
self.nodes2 = [] # 节点对象

# 初始化城市节点
for i in range(len(distance_x)):
# 在画布上随机初始坐标
x = distance_x[i]
y = distance_y[i]
self.nodes.append((x, y))
# 生成节点椭圆,半径为self.__r
node = self.canvas.create_oval(x - self.__r,
y - self.__r, x + self.__r, y + self.__r,
fill = "#ff0000", # 填充红色
outline = "#000000", # 轮廓白色
tags = "node",
)
self.nodes2.append(node)
# 显示坐标
self.canvas.create_text(x,y-10, # 使用create_text方法在坐标(302,77)处绘制文字
text = '('+str(x)+','+str(y)+')', # 所绘制文字的内容
fill = 'black' # 所绘制文字的颜色为灰色
)

# 顺序连接城市
#self.line(range(city_num))

# 初始城市之间的距离和信息素
for i in range(city_num):
for j in range(city_num):
pheromone_graph[i][j] = 1.0

self.ants = [Ant(ID) for ID in range(ant_num)] # 初始蚁群
self.best_ant = Ant(-1) # 初始最优解
self.best_ant.total_distance = 1 << 31 # 初始最大距离
self.iter = 1 # 初始化迭代次数

# 将节点按order顺序连线
def line(self, order):
# 删除原线
self.canvas.delete("line")
def line2(i1, i2):
p1, p2 = self.nodes[i1], self.nodes[i2]
self.canvas.create_line(p1, p2, fill = "#000000", tags = "line")
return i2

# order[-1]为初始值
reduce(line2, order, order[-1])

# 清除画布
def clear(self):
for item in self.canvas.find_all():
self.canvas.delete(item)

# 退出程序
def quite(self, evt):
self.__lock.acquire()
self.__running = False
self.__lock.release()
self.root.destroy()
print (u"\n程序已退出...")
sys.exit()

# 停止搜索
def stop(self, evt):
self.__lock.acquire()
self.__running = False
self.__lock.release()

# 开始搜索
def search_path(self, evt = None):

# 开启线程
self.__lock.acquire()
self.__running = True
self.__lock.release()

while self.__running:
# 遍历每一只蚂蚁
for ant in self.ants:
# 搜索一条路径
ant.search_path()
# 与当前最优蚂蚁比较
if ant.total_distance < self.best_ant.total_distance:
# 更新最优解
self.best_ant = copy.deepcopy(ant)
# 更新信息素
self.__update_pheromone_gragh()
print (u"迭代次数:",self.iter,u"最佳路径总距离:",int(self.best_ant.total_distance))
# 连线
self.line(self.best_ant.path)
# 设置标题
self.title("TSP蚁群算法(n:随机初始 e:开始搜索 s:停止搜索 q:退出程序) 迭代次数: %d" % self.iter)
# 更新画布
self.canvas.update()
self.iter += 1

# 更新信息素
def __update_pheromone_gragh(self):

# 获取每只蚂蚁在其路径上留下的信息素
temp_pheromone = [[0.0 for col in range(city_num)] for raw in range(city_num)]
for ant in self.ants:
for i in range(1,city_num):
start, end = ant.path[i-1], ant.path[i]
# 在路径上的每两个相邻城市间留下信息素,与路径总距离反比
temp_pheromone[start][end] += Q / ant.total_distance
temp_pheromone[end][start] = temp_pheromone[start][end]

# 更新所有城市之间的信息素,旧信息素衰减加上新迭代信息素
for i in range(city_num):
for j in range(city_num):
pheromone_graph[i][j] = pheromone_graph[i][j] * RHO + temp_pheromone[i][j]

# 主循环
def mainloop(self):
self.root.mainloop()

#----------- 程序的入口处 -----------

if __name__ == '__main__':

TSP(tkinter.Tk()).mainloop()

Reference:
https://zh.wikipedia.org/wiki/蚁群算法
https://blog.csdn.net/fanxin_i/article/details/80380733
https://blog.csdn.net/qq_35109096/article/details/81126925

算法简介

遗传算法(Genetic Alogrithm,GA)是最早由美国Holland教授提出的一种基于自然界的“适者生存,优胜劣汰”基本法则的智能搜索算法。该法则很好地诠释了生物进化的自然选择过程。遗传算法也是借鉴该基本法则,通过基于种群的思想,将问题的解通过编码的方式转化为种群中的个体,并让这些个体不断地通过选择、交叉和变异算子模拟生物的进化过程,然后利用“优胜劣汰”法则选择种群中适应性较强的个体构成子种群,然后让子种群重复类似的进化过程,直到找到问题的最优解或者到达一定的进化(运算)时间。

重要概念

个体(染色体):自然界中一个个体(染色体)代表一个生物,在GA算法中,个体(染色体)代表了具体问题的一个解。

基因:在GA算法中,基因代表了具体问题解的一个决策变量,问题解和染色体中基因的对应关系如下所示

种群:多个个体即组成一个种群。GA算法中,一个问题的多组解即构成了问题的解的种群。

主要步骤

主要操作介绍

种群的初始化

选择一种编码方案,然后在解空间内通过随机生成的方式初始化一定数量的个体构成GA的种群
种群的初始化和具体问题相关,一般来说可以采取某种分布(如高斯分布)在一定求解范围内随机获取

种群评价

种群的评价即计算种群中个体的适应度值。假设种群population有popsize个个体。依次计算每个个体的适应度值及评价种群。或者利用启发式算法对种群中的个体(矩形件的排入顺序)生成排样图并依此计算个体的适应函数值(利用率),然后保存当前种群中的最优个体作为搜索到的最优解。

选择操作

常见的选择操作有轮盘赌的方式:根据个体的适应度计算被选中的概率,公式如下:

$$P(x_j)=\frac{fit(x_j)}{\sum_{i=1}^n fit(x_i)}, j\in{1,2,…,n}$$

交叉操作

一般以概率阀值Pc控制是否进行单点交叉、多点交叉或者其他交叉方式生成新的交叉个体。

交叉操作也有许多种:单点交叉,两点交叉等。此处仅讲解一下两点交叉。首先利用选择操作从种群中选择两个父辈个体parent1和parent2,然后随机产生两个位置pos1和pos2,将这两个位置中间的基因位信息进行交换,便得到下图所示的off1和off2两个个体,但是这两个个体中一般会存在基因位信息冲突的现象(整数编码时),此时需要对off1和off2个体进行调整:off1中的冲突基因根据parent1中的基因调整为parent2中的相同位置处的基因。如off1中的“1”出现了两次,则第二处的“1”需要调整为parent1中“1”对应的parent2中的“4”,依次类推处理off1中的相冲突的基因。需要注意的是,调整off2,则需要参考parent2。

变异操作

一般以概率阀值Pm控制是否对个体的部分基因执行单点变异或多点变异。

变异操作的话,根据不同的编码方式有不同的变异操作。

如果是浮点数编码,则变异可以就染色体中间的某一个基因位的信息进行变异(重新生成或者其他调整方案)。

如果是采用整数编码方案,则一般有多种变异方法:位置变异(调换同一个体的多个基因)和符号变异(正数变负数)。

python实现

求 $f(x,y)=21.5+x\times\sin(4\times\pi\times x) + y \times\sin(20\times\pi\times y)$ 的最大值

代码来自:
https://blog.csdn.net/qq_30666517/article/details/78637255

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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# -*- coding:utf-8 -*- 
import numpy as np
from scipy.optimize import fsolve, basinhopping
import random
import timeit


# 根据解的精度确定染色体(chromosome)的长度
# 需要根据决策变量的上下边界来确定
def getEncodedLength(delta=0.0001, boundarylist=[]):
# 每个变量的编码长度
lengths = []
for i in boundarylist:
lower = i[0]
upper = i[1]
# lamnda 代表匿名函数f(x)=0,50代表搜索的初始解
res = fsolve(lambda x: ((upper - lower) * 1 / delta) - 2 ** x - 1, 50)
length = int(np.floor(res[0]))
lengths.append(length)
return lengths


# 随机生成初始编码种群
def getIntialPopulation(encodelength, populationSize):
# 随机化初始种群为0
chromosomes = np.zeros((populationSize, sum(encodelength)), dtype=np.uint8)
for i in range(populationSize):
chromosomes[i, :] = np.random.randint(0, 2, sum(encodelength))
# print('chromosomes shape:', chromosomes.shape)
return chromosomes


# 染色体解码得到表现型的解
def decodedChromosome(encodelength, chromosomes, boundarylist, delta=0.0001):
populations = chromosomes.shape[0]
variables = len(encodelength)
decodedvalues = np.zeros((populations, variables))
for k, chromosome in enumerate(chromosomes):
chromosome = chromosome.tolist()
start = 0
for index, length in enumerate(encodelength):
# 将一个染色体进行拆分,得到染色体片段
power = length - 1
# 解码得到的10进制数字
demical = 0
for i in range(start, length + start):
demical += chromosome[i] * (2 ** power)
power -= 1
lower = boundarylist[index][0]
upper = boundarylist[index][1]
decodedvalue = lower + demical * (upper - lower) / (2 ** length - 1)
decodedvalues[k, index] = decodedvalue
# 开始去下一段染色体的编码
start = length
return decodedvalues


# 得到个体的适应度值及每个个体被选择的累积概率
def getFitnessValue(func, chromosomesdecoded):
# 得到种群规模和决策变量的个数
population, nums = chromosomesdecoded.shape
# 初始化种群的适应度值为0
fitnessvalues = np.zeros((population, 1))
# 计算适应度值
for i in range(population):
fitnessvalues[i, 0] = func(chromosomesdecoded[i, :])
# 计算每个染色体被选择的概率
probability = fitnessvalues / np.sum(fitnessvalues)
# 得到每个染色体被选中的累积概率
cum_probability = np.cumsum(probability)
return fitnessvalues, cum_probability


# 新种群选择
def selectNewPopulation(chromosomes, cum_probability):
m, n = chromosomes.shape
newpopulation = np.zeros((m, n), dtype=np.uint8)
# 随机产生M个概率值
randoms = np.random.rand(m)
for i, randoma in enumerate(randoms):
logical = cum_probability >= randoma
index = np.where(logical == 1)
# index是tuple,tuple中元素是ndarray
newpopulation[i, :] = chromosomes[index[0][0], :]
return newpopulation


# 新种群交叉
def crossover(population, Pc=0.8):
"""
:param population: 新种群
:param Pc: 交叉概率默认是0.8
:return: 交叉后得到的新种群
"""
# 根据交叉概率计算需要进行交叉的个体个数
m, n = population.shape
numbers = np.uint8(m * Pc)
# 确保进行交叉的染色体个数是偶数个
if numbers % 2 != 0:
numbers += 1
# 交叉后得到的新种群
updatepopulation = np.zeros((m, n), dtype=np.uint8)
# 产生随机索引
index = random.sample(range(m), numbers)
# 不进行交叉的染色体进行复制
for i in range(m):
if not index.__contains__(i):
updatepopulation[i, :] = population[i, :]
# crossover
while len(index) > 0:
a = index.pop()
b = index.pop()
# 随机产生一个交叉点
crossoverPoint = random.sample(range(1, n), 1)
crossoverPoint = crossoverPoint[0]
# one-single-point crossover
updatepopulation[a, 0:crossoverPoint] = population[a, 0:crossoverPoint]
updatepopulation[a, crossoverPoint:] = population[b, crossoverPoint:]
updatepopulation[b, 0:crossoverPoint] = population[b, 0:crossoverPoint]
updatepopulation[b, crossoverPoint:] = population[a, crossoverPoint:]
return updatepopulation


# 染色体变异
def mutation(population, Pm=0.01):
"""
:param population: 经交叉后得到的种群
:param Pm: 变异概率默认是0.01
:return: 经变异操作后的新种群
"""
updatepopulation = np.copy(population)
m, n = population.shape
# 计算需要变异的基因个数
gene_num = np.uint8(m * n * Pm)
# 将所有的基因按照序号进行10进制编码,则共有m*n个基因
# 随机抽取gene_num个基因进行基本位变异
mutationGeneIndex = random.sample(range(0, m * n), gene_num)
# 确定每个将要变异的基因在整个染色体中的基因座(即基因的具体位置)
for gene in mutationGeneIndex:
# 确定变异基因位于第几个染色体
chromosomeIndex = gene // n
# 确定变异基因位于当前染色体的第几个基因位
geneIndex = gene % n
# mutation
if updatepopulation[chromosomeIndex, geneIndex] == 0:
updatepopulation[chromosomeIndex, geneIndex] = 1
else:
updatepopulation[chromosomeIndex, geneIndex] = 0
return updatepopulation


# 定义适应度函数
def fitnessFunction():
return lambda x: 21.5 + x[0] * np.sin(4 * np.pi * x[0]) + x[1] * np.sin(20 * np.pi * x[1])


def main(max_iter=500):
# 每次迭代得到的最优解
optimalSolutions = []
optimalValues = []
# 决策变量的取值范围
decisionVariables = [[-3.0, 12.1], [4.1, 5.8]]
# 得到染色体编码长度
lengthEncode = getEncodedLength(boundarylist=decisionVariables)

# 得到初始种群编码
chromosomesEncoded = getIntialPopulation(lengthEncode, 10)

for iteration in range(max_iter):
# 种群解码
decoded = decodedChromosome(lengthEncode, chromosomesEncoded, decisionVariables)
# 得到个体适应度值和个体的累积概率
evalvalues, cum_proba = getFitnessValue(fitnessFunction(), decoded)
# 选择新的种群
newpopulations = selectNewPopulation(chromosomesEncoded, cum_proba)
# 进行交叉操作
crossoverpopulation = crossover(newpopulations)
# mutation
mutationpopulation = mutation(crossoverpopulation)
# 将变异后的种群解码,得到每轮迭代最终的种群
final_decoded = decodedChromosome(lengthEncode, mutationpopulation, decisionVariables)
# 适应度评价
fitnessvalues, cum_individual_proba = getFitnessValue(fitnessFunction(), final_decoded)
# 搜索每次迭代的最优解,以及最优解对应的目标函数的取值
optimalValues.append(np.max(list(fitnessvalues)))
index = np.where(fitnessvalues == max(list(fitnessvalues)))
optimalSolutions.append(final_decoded[index[0][0], :])
chromosomesEncoded = mutationpopulation

# 搜索最优解
optimalValue = np.max(optimalValues)
optimalIndex = np.where(optimalValues == optimalValue)
optimalSolution = optimalSolutions[optimalIndex[0][0]]
return optimalSolution, optimalValue


solution, value = main()
print('最优解: x1, x2')
print(solution[0], solution[1])
print('最优目标函数值:', value)
# 测量运行时间
elapsedtime = timeit.timeit(stmt=main, number=1)
print('Searching Time Elapsed:(S)', elapsedtime)

Reference:
https://blog.csdn.net/bible_reader/article/details/72782675
https://blog.csdn.net/qq_30666517/article/details/78637255

算法简介

模拟退火算法(Simulated annealing algorithm,SAA)的思想借鉴于固体的退火原理,当固体的温度很高的时候,内能比较大,固体的内部粒子处于快速无序运动,当温度慢慢降低的过程中,固体的内能减小,粒子的慢慢趋于有序,最终,当固体处于常温时,内能达到最小,此时,粒子最为稳定。模拟退火算法便是基于这样的原理设计而成。

模拟退火算法从某一高温出发,在高温状态下计算初始解,然后以预设的邻域函数产生一个扰动量,从而得到新的状态,即模拟粒子的无序运动,比较新旧状态下的能量,即目标函数的解。如果新状态的能量小于旧状态,则状态发生转化;如果新状态的能量大于旧状态,则以一定的概率准则发生转化。当状态稳定后,便可以看作达到了当前状态的最优解,便可以开始降温,在下一个温度继续迭代,最终达到低温的稳定状态,便得到了模拟退火算法产生的结果。

算法过程

状态空间与邻域函数

状态空间也称为搜索空间,它由经过编码的可行解的集合所组成。而邻域函数应尽可能满足产生的候选解遍布全部状态空间。其通常由产生候选解的方式和候选解产生的概率分布组成。候选解一般按照某一概率密度函数对解空间进行随机采样获得,而概率分布可以为均匀分布、正态分布、指数分布等。

状态概率分布(Metropolis准则)

状态转移概率是指从一个状态转换成另一个状态的概率,模拟退火算法中一般采用Metropolis准则,具体如下:

$$f(x)=\left\lbrace\begin{array}{cll}
1 & , & E(x_{new}) < E(x_{old}) \\
exp(-\frac{E(x_{new})-E(x_{old})}{T}) & , & E(x_{new}) \ge E(x_{old})
\end{array}\right.$$

其与当前温度参数T有关,随温度的下降而减小。

冷却进度表

冷却进度表是指从某一高温状态T向低温状态冷却时的降温函数,设时刻的温度为T(t),则经典模拟退火算法的降温方式为:

$$T(t)=\frac{T_{0}}{lg(1+t)}$$

而快速模拟退火算法的降温方式为:

$$T(t)=\frac{T_{0}}{1+t}$$

其他方法不再赘述。

初始温度

一般来说,初始温度越大,获得高质量解的几率越大,但是花费的时间也会随之增加,因此,初温的确定应该同时考虑计算效率与优化质量,常用的方法包括:

1.均匀抽样一组状态,以各状态目标值的方差为初温。

2.随机产生一组状态,确定两两状态间的最大目标值差,然后根据差值,利用一定的函数确定初温,如: $T_{0}=-\frac{\Delta_{max}}{Pr}$ ,其中Pr为初始接受概率。

3.根据经验公式给出。

循环终止准则

内循环(求解循环)终止准则:

  1. 检验目标函数的均值是否稳定
  2. 连续若干步的目标值变化较小
  3. 按一定的步数进行抽样

外循环(降温循环)终止准则:

  1. 设置终止温度
  2. 设置外循环迭代次数
  3. 算法搜索到的最优值连续若干步保持不变
  4. 检验系统熵是否稳定

Python实现

实例函数: $f(x)=(x^{2}-5x)sin(x^2)$

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
import numpy as np
import matplotlib.pyplot as plt
import random

class SA(object):

def __init__(self, interval, tab='min', T_max=10000, T_min=1, iterMax=1000, rate=0.95):
self.interval = interval # 给定状态空间 - 即待求解空间
self.T_max = T_max # 初始退火温度 - 温度上限
self.T_min = T_min # 截止退火温度 - 温度下限
self.iterMax = iterMax # 定温内部迭代次数
self.rate = rate # 退火降温速度

self.x_seed = random.uniform(interval[0], interval[1]) # 解空间内的种子
self.tab = tab.strip() # 求解最大值还是最小值的标签: 'min' - 最小值;'max' - 最大值

self.solve() # 完成主体的求解过程
self.display() # 数据可视化展示

def solve(self):
temp = 'deal_' + self.tab # 采用反射方法提取对应的函数
if hasattr(self, temp):
deal = getattr(self, temp)
else:
exit('>>>tab标签传参有误:"min"|"max"<<<')
x1 = self.x_seed
T = self.T_max
while T >= self.T_min:
for i in range(self.iterMax):
f1 = self.func(x1)
delta_x = random.random() * 2 - 1 # [-1,1)之间的随机值
if x1 + delta_x >= self.interval[0] and x1 + delta_x <= self.interval[1]: # 将随机解束缚在给定状态空间内
x2 = x1 + delta_x
else:
x2 = x1 - delta_x
f2 = self.func(x2)
delta_f = f2 - f1
x1 = deal(x1, x2, delta_f, T)
T *= self.rate
self.x_solu = x1 # 提取最终退火解

def func(self, x): # 状态产生函数 - 即待求解函数
value = np.sin(x**2) * (x**2 - 5*x)
return value

def p_min(self, delta, T): # 计算最小值时,容忍解的状态迁移概率
probability = np.exp(-delta/T)
return probability

def p_max(self, delta, T):
probability = np.exp(delta/T) # 计算最大值时,容忍解的状态迁移概率
return probability

def deal_min(self, x1, x2, delta, T):
if delta < 0: # 更优解
return x2
else: # 容忍解
P = self.p_min(delta, T)
if P > random.random(): return x2
else: return x1

def deal_max(self, x1, x2, delta, T):
if delta > 0: # 更优解
return x2
else: # 容忍解
P = self.p_max(delta, T)
if P > random.random(): return x2
else: return x1

def display(self):
print('seed: {}\nsolution: {}'.format(self.x_seed, self.x_solu))
plt.figure(figsize=(6, 4))
x = np.linspace(self.interval[0], self.interval[1], 300)
y = self.func(x)
plt.plot(x, y, 'g-', label='function')
plt.plot(self.x_seed, self.func(self.x_seed), 'bo', label='seed')
plt.plot(self.x_solu, self.func(self.x_solu), 'r*', label='solution')
plt.title('solution = {}'.format(self.x_solu))
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.savefig('SA.png', dpi=500)
plt.show()
plt.close()


if __name__ == '__main__':
SA([-5, 5], 'max')

Reference
https://www.imooc.com/article/30160
https://baike.baidu.com/item/模拟退火算法/355508?fr=aladdin
https://blog.csdn.net/google19890102/article/details/45395257
https://www.cnblogs.com/xxhbdk/p/9192750.html

算法简介

粒子群算法(Particle swarm optimization,PSO)是模拟群体智能所建立起来的一种优化算法,主要用于解决最优化问题(optimization problems)。1995年由 Eberhart和Kennedy 提出,是基于对鸟群觅食行为的研究和模拟而来的。

假设一群鸟在觅食,在觅食范围内,只在一个地方有食物,所有鸟儿都看不到食物(即不知道食物的具体位置。当然不知道了,知道了就不用觅食了),但是能闻到食物的味道(即能知道食物距离自己是远是近。鸟的嗅觉是很灵敏的)。

假设鸟与鸟之间能共享信息(即互相知道每个鸟离食物多远。这个是人工假定,实际上鸟们肯定不会也不愿意),那么最好的策略就是结合自己离食物最近的位置和鸟群中其他鸟距离食物最近的位置这2个因素综合考虑找到最好的搜索位置。

粒子群算法与《遗传算法》等进化算法有很多相似之处。也需要初始化种群,计算适应度值,通过进化进行迭代等。但是与遗传算法不同,它没有交叉,变异等进化操作。与遗传算法比较,PSO的优势在于很容易编码,需要调整的参数也很少。

核心概念

PSO有几个核心概念:

  1. 粒子(particle):一只鸟。类似于遗传算法中的个体。
  2. 种群(population):一群鸟。类似于遗传算法中的种群。
  3. 位置(position):一个粒子(鸟)当前所在的位置。
  4. 经验(best):一个粒子(鸟)自身曾经离食物最近的位置。
  5. 速度(velocity ):一个粒子(鸟)飞行的速度。
  6. 适应度(fitness):一个粒子(鸟)距离食物的远近。与遗传算法中的适应度类似。

算法过程

算法说明

两个核心公式

加速度更新公式:

$$v[i] = w * v[i] + c1 * rand() *(pbest[i] - present[i]) + c2 * rand() * (gbest - present[i])$$

其中v[i]代表第i个粒子的速度,w代表惯性权值,c1和c2表示学习参数,rand()表示在0-1之间的随机数,pbest[i]代表第i个粒子搜索到的最优值,gbest代表整个集群搜索到的最优值,present[i]代表第i个粒子的当前位置。

位置更新公式:

$$present[i]=present[i]+v[i]$$

解释说明

1.粒子数:粒子数的选取一般在20个到40个之间,但是需要具体问题具体对待,如果对于复杂问题,则需要设置更多的粒子,粒子数量越多,其搜索范围就越大。

2.惯性因子 $w$ :用来控制继承多少粒子当前的速度的,越大则对于当前速度的继承程度越小,越小则对于当前速度的继承程度越大。有些同学可能会产生疑问,是不是说反了。其实不是,从公式中可以明确看出,其值越大,则速度的改变幅度就越大,则对于粒子的当前速度继承越小;反之,速度的改变幅度越小,则对于粒子当前速度继承越大。因此如果的值越大,则解的搜索范围越大,可以提高算法的全局搜索能力,但也损失了局部搜索能力,有可能错失最优解;反之如果的值越小,则解的搜索范围也就越小,算法的全局搜索能力也就越小,容易陷入局部最优。如果是变量,则其值应该随着迭代次数的增加而减小(类似于梯度下降当中的学习率)。如果为定值,则建议在0.6到0.75之间进行选取。

3.加速常数 $c1,c2$ :通过公式一可以看出,加速常数控制着飞翔速度的计算是更加看重自身经验还是群体经验。公式一中的第二项就是自身经验的体现,加速常数可以看做是用来调整自身经验在计算粒子飞翔速度上的权重。同理是用来控制群体经验在计算粒子飞翔速度过程中的权重的。如果为0,则自身经验对于速度的计算不起作用,如果为0,则群体经验对于粒子飞翔速度的计算不起作用。的取值在学术界分歧很大主要有如下几种情况:

学者 参数取值
Clerc c1=c2=2.05
Carlisle c1=2.8, c2=1.3
Trelea w=0.6, c1=c2=1.7
Eberhart w=0.729, c1=c2=1.494

python实现

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
# -*- coding: utf-8 -*-
"""
f(x1,x2) = x1**2 + x2**2, x1,x2 belongs to [-10,10],求Min f
"""

import matplotlib.pyplot as plt
import numpy as np


class PSO(object):
def __init__(self, population_size, max_steps):
self.w = 0.6 # 惯性权重
self.c1 = self.c2 = 2
self.population_size = population_size # 粒子群数量
self.dim = 2 # 搜索空间的维度
self.max_steps = max_steps # 迭代次数
self.x_bound = [-10, 10] # 解空间范围
self.x = np.random.uniform(self.x_bound[0], self.x_bound[1],
(self.population_size, self.dim)) # 初始化粒子群位置
self.v = np.random.rand(self.population_size, self.dim) # 初始化粒子群速度
fitness = self.calculate_fitness(self.x)
self.p = self.x # 个体的最佳位置
self.pg = self.x[np.argmin(fitness)] # 全局最佳位置
self.individual_best_fitness = fitness # 个体的最优适应度
self.global_best_fitness = np.max(fitness) # 全局最佳适应度

def calculate_fitness(self, x):
return np.sum(np.square(x), axis=1)

def evolve(self):
fig = plt.figure()
for step in range(self.max_steps):
r1 = np.random.rand(self.population_size, self.dim)
r2 = np.random.rand(self.population_size, self.dim)
# 更新速度和权重
self.v = self.w*self.v+self.c1*r1*(self.p-self.x)+self.c2*r2*(self.pg-self.x)
self.x = self.v + self.x
plt.clf()
plt.scatter(self.x[:, 0], self.x[:, 1], s=30, color='k')
plt.xlim(self.x_bound[0], self.x_bound[1])
plt.ylim(self.x_bound[0], self.x_bound[1])
plt.pause(0.01)
# plt.ion()
# plt.show()

fitness = self.calculate_fitness(self.x)
# 需要更新的个体
update_id = np.greater(self.individual_best_fitness, fitness)
self.p[update_id] = self.x[update_id]
self.individual_best_fitness[update_id] = fitness[update_id]
# 新一代出现了更小的fitness,所以更新全局最优fitness和位置
if np.min(fitness) < self.global_best_fitness:
self.pg = self.x[np.argmin(fitness)]
self.global_best_fitness = np.min(fitness)
print('best fitness: %.5f, mean fitness: %.5f' % (self.global_best_fitness, np.mean(fitness)))

pso = PSO(10, 100)
pso.evolve()

Reference:
https://blog.csdn.net/zhaozx19950803/article/details/79854466
https://blog.csdn.net/yy2050645/article/details/80740641
https://blog.csdn.net/zj15527620802/article/details/81366105

英文: Quality of Service
中文: 服务质量
介绍: 指一个网络能够利用各种基础技术,为指定的网络通信提供更好的服务能力,是网络的一种安全机制,是用来解决网络延迟和阻塞等问题的一种技术。 通过配置QoS,对企业的网络流量进行调控,避免并管理网络拥塞,减少报文的丢失率,同时也可以为企业用户提供专用带宽或者为不同的业务(语音、视频、数据等)提供差分服务。

在正常情况下,如果网络只用于特定的无时间限制的应用系统,并不需要QoS,比如Web应用,或E-mail设置等。但是对关键应用和多媒体应用就十分必要。当网络过载或拥塞时,==QoS能确保重要业务量不受延迟或丢弃==,同时保证网络的高效运行。在RFC 3644上有对QoS的说明。

可用性(Usability)

是当用户需要时网络即能工作的时间百分比。可用性主要是设备可靠性和网络存活性相结合的结果。对它起作用的还有一些其他因素,包括软件稳定性以及网络演进或升级时不中断服务的能力。 在连续5min内,如果一个IP网络所提供的丢包率<=75%,则认为该时间段是可用的,否则是不可用的。

吞吐量(网络带宽)(Throughput)

网络带宽是指在单位时间(一般指的是1秒钟)内能传输的数据量。对IP网而言可以从帧中继网借用一些概念。根据应用和服务类型,服务水平协议(SLA)可以规定承诺信息速率(CIR)、突发信息速率(BIR)和最大突发信号长度。承诺信息速率是应该予以严格保证的,对突发信息速率可以有所限定,以在容纳预定长度突发信号的同时容纳从话音到视像以及一般数据的各种服务。一般讲,吞吐量越大越好。

时延(Latency)(Packet delay)

指一项服务从网络入口到出口的平均经过时间。许多服务,特别是话音和视像等实时服务都是高度不能容忍时延的。当时延超过200-250毫秒时,交互式会话是非常麻烦的。为了提供高质量话音和会议电视,网络设备必须能保证低的时延。
产生时延的因素很多,包括分组时延、排队时延、交换时延和传播时延。传播时延是信息通过铜线、光纤或无线链路所需的时间,它是光速的函数。在任何系统中,包括同步数字系列(SDH)、异步传输模式(ATM)和弹性分组环路(RPR),传播时延总是存在的。

时延变化(抖动)(Packet delay variation)

是指同一业务流中不同分组所呈现的时延不同。高频率的时延变化称作抖动,而低频率的时延变化称作漂移。抖动主要是由于业务流中相继分组的排队等候时间不同引起的,是对服务质量影响最大的一个问题。

​某些业务类型(特别是语音和视频等实时业务)是极其不能容忍抖动的。报文到达时间的差异将在语音或视频中造成断续;另外,抖动也会影响一些网络协议的处理,有些协议是按固定的时间间隔发送交互性报文,抖动过大就会导致协议震荡,而实际上所有传输系统都有抖动,但只要抖动在规定容差之内就不会影响服务质量,另外,可利用缓存来克服过量的抖动,但这将会增加时延。

漂移是任何同步传输系统都有的一个问题。在SDH系统中是通过严格的全网分级定时来克服漂移的。在异步系统中,漂移一般不是问题。漂移会造成基群失帧,使服务质量的要求不能满足。

丢包(Packet loss)

不管是比特丢失还是分组丢失,对分组数据业务的影响比对实时业务的影响都大。在通话期间,丢失一个比特或一个分组的信息往往用户注意不到。在视像广播期间,这在屏幕上可能造成瞬间的波形干扰,然后视像很快恢复如初。即便是用传输控制协议(TCP)传送数据也能处理丢失,因为传输控制协议允许丢失的信息重发。事实上,一种叫做随机早丢(RED)的拥塞控制机制在故意丢失分组,其目的是在流量达到设定门限时抑制TCP传输速率,减少拥塞,同时还使TCP流失去同步,以防止因速率窗口的闭合引起吞吐量摆动。但分组丢失多了,会影响传输质量。所以,要保持统计数字,当超过预定门限时就向网络管理人员告警。

丢包(packetloss)可能在所有环节中发生,例如:

  • 处理过程:路由器在收到报文的时候可能由于CPU繁忙,无法处理报文而导致丢包;
  • 排队过程:在把报文调度到队列的时候可能由于队列被装满而导致丢包;
  • 传输过程:报文在链路上传输的过程中,可能由于种种原因(如链路故障等)导致的丢包。

References:
https://blog.csdn.net/qq_25077833/article/details/53428655
https://blog.csdn.net/kakingka/article/details/45698709
https://blog.csdn.net/qq_38265137/article/details/80466737

本文主要参考官方文档https://lxml.de/tutorial.html整理,如有错误欢迎指出。

1
2
from lxml import etree
from copy import deepcopy
1
2
3
4
5
6
7
8
9
10
11
12
13
# 添加Element默认添加为根节点
root = etree.Element("root")
# 通过append添加子节点
root.append(etree.Element("child1"))
# 通过SubElement添加子节点
child2 = etree.SubElement(root, "child2")
child3 = etree.SubElement(root, "child3")
print(etree.tostring(root))
print(etree.tostring(root, pretty_print=True))
# 默认encoding是ASCII
print(etree.tostring(root, encoding='iso-8859-1'))
# 如果要把byte转成str输出的话,可以用下边的语句
print(str(etree.tostring(root, pretty_print=True),encoding='utf-8'))
b'<root><child1/><child2/><child3/></root>'
b'<root>\n  <child1/>\n  <child2/>\n  <child3/>\n</root>\n'
b"<?xml version='1.0' encoding='iso-8859-1'?>\n<root><child1/><child2/><child3/></root>"
<root>
  <child1/>
  <child2/>
  <child3/>
</root>
1
2
3
4
5
6
7
# 每个元素都是一个list
child1 = root[0]
# .tag 可以取出元素的标签
print(child1.tag, len(root))
# 大部分list方法都可以使用在element上
root.insert(0, etree.Element("child0"))
print(root[0].tag)
child1 3
child0
1
2
3
4
# 可以用list函数取出子元素的list
children = list(root)
print(root)
print(children)
<Element root at 0x107b03888>
[<Element child0 at 0x107b034c8>, <Element child1 at 0x107b03448>, <Element child2 at 0x107b038c8>, <Element child3 at 0x107b03908>]
1
2
3
4
5
6
7
# 通过长度检查element是否为叶子节点
if len(root):
print('root has children')
if len(child1):
print('child1 has children')
else:
print('child1 has no child')
root has children
child1 has no child
1
2
3
4
5
6
7
8
9
10
# 不同于普通list,赋值可能会造成元素移动
print(etree.tostring(root))
root[0] = root[-1]
print(etree.tostring(root))

# 这是普通list
l = [0,1,2,3]
print(l)
l[0] = l[-1]
print(l)
b'<root><child0/><child1/><child2/><child3/></root>'
b'<root><child3/><child1/><child2/></root>'
[0, 1, 2, 3]
[3, 1, 2, 3]
1
2
3
4
5
# 要拷贝节点,需要调用deepcopy
newroot = etree.Element('newroot')
newroot.append(deepcopy(root[1]))
print(etree.tostring(root))
print(etree.tostring(newroot))
b'<root><child3/><child1/><child2/></root>'
b'<newroot><child1/></newroot>'
1
2
3
4
# etree元素自带方法可以找到对应的父节点、前一个节点、后一个节点
print(root is child1.getparent())
print(root[0] is root[1].getprevious())
print(root[1] is root[0].getnext())
True
True
True
1
2
3
4
5
6
7
8
9
# 属性以字典方式存储
root = etree.Element("root", interesting="totally")
print(etree.tostring(root))
# 通过get方法获取属性值
print(root.get("interesting"))
print(root.get("hello"))
# 通过set方法添加属性
root.set("hello", "Huhu")
print(etree.tostring(root))
b'<root interesting="totally"/>'
totally
None
b'<root interesting="totally" hello="Huhu"/>'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 字典的相关方法也可以直接使用
print(root.items(), root.keys(), root.values())

# 通过 attrib 可以直接取出属性进行操作
attributes = root.attrib
print(attributes.get("no-such-attribute"))
None
attributes["hello"] = "Guten Tag"
print(attributes["hello"])
# root中的属性一并修改
print(root.get("hello"))

# 也可以转化为纯正的dict
d = dict(root.attrib)
print(d.items())
[('interesting', 'totally'), ('hello', 'Huhu')] ['interesting', 'hello'] ['totally', 'Huhu']
None
Guten Tag
Guten Tag
dict_items([('interesting', 'totally'), ('hello', 'Guten Tag')])
1
2
3
4
5
# 元素中包含文本
root = etree.Element("root")
root.text = "Hello World"
print(root.text)
print(etree.tostring(root))
Hello World
b'<root>Hello World</root>'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 如何添加特殊元素类似于<br/>
html = etree.Element("html")
body = etree.SubElement(html, "body")
body.text = "TEXT"

print(etree.tostring(html))

br = etree.SubElement(body, "br")
print(etree.tostring(html))

br.tail = "TAIL"
print(etree.tostring(html))
body.text = "HEAD"
print(etree.tostring(html))
b'<html><body>TEXT</body></html>'
b'<html><body>TEXT<br/></body></html>'
b'<html><body>TEXT<br/>TAIL</body></html>'
b'<html><body>HEAD<br/>TAIL</body></html>'
1
2
3
4
# tail其实属于前边元素的一部分
print(etree.tostring(br))
print(etree.tostring(br, with_tail=False))
print(etree.tostring(html, method="text"))
b'<br/>TAIL'
b'<br/>'
b'HEADTAIL'
1
2
3
# xpath方法也可以使用
print(html.xpath("string()"))
print(html.xpath("//text()"))
HEADTAIL
['HEAD', 'TAIL']
1
2
3
# 甚至可以吧xpath的方法包装成函数
build_text_list = etree.XPath("//text()") # lxml.etree only!
print(build_text_list(html))
['HEAD', 'TAIL']
1
2
3
4
5
6
7
8
9
# text也可以找爸爸
texts = build_text_list(html)
print(texts[0])

parent = texts[0].getparent()
print(parent.tag)

print(texts[1])
print(texts[1].getparent().tag)
HEAD
body
TAIL
br
1
2
3
4
# 判断一段文本是正常的文本还是tail
print(texts[0].is_text)
print(texts[1].is_text)
print(texts[1].is_tail)
True
False
True
1
2
3
4
# 但是XPath下有些方法就不行了
stringify = etree.XPath("string()")
print(stringify(html))
print(stringify(html).getparent())
HEADTAIL
None
1
2
3
4
5
6
7
8
9
# etree中的树是iterable的
root = etree.Element("root")
etree.SubElement(root, "child").text = "Child 1"
etree.SubElement(root, "child").text = "Child 2"
etree.SubElement(root, "another").text = "Child 3"

print(etree.tostring(root, pretty_print=True))
for element in root.iter():
print("%s - %s" % (element.tag, element.text))
b'<root>\n  <child>Child 1</child>\n  <child>Child 2</child>\n  <another>Child 3</another>\n</root>\n'
root - None
child - Child 1
child - Child 2
another - Child 3
1
2
3
4
5
# 可以通过在iter中添加参数,来过滤输出的tag
for element in root.iter("child"):
print("%s - %s" % (element.tag, element.text))
for element in root.iter("another", "child"):
print("%s - %s" % (element.tag, element.text))
child - Child 1
child - Child 2
child - Child 1
child - Child 2
another - Child 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 默认情况下,所有节点都会被遍历,如Entity、Comment等,通过添加tag参数可以避免这一点
root.append(etree.Entity("#234"))
root.append(etree.Comment("some comment"))
print(etree.tostring(root))

for element in root.iter():
if isinstance(element.tag, str):
print("%s - %s" % (element.tag, element.text))
else:
print("SPECIAL: %s - %s" % (element, element.text))

for element in root.iter(tag=etree.Element):
print("%s - %s" % (element.tag, element.text))

for element in root.iter(tag=etree.Entity):
print(element.text)
b'<root><child>Child 1</child><child>Child 2</child><another>Child 3</another>&#234;<!--some comment--></root>'
root - None
child - Child 1
child - Child 2
another - Child 3
SPECIAL: &#234; - &#234;
SPECIAL: <!--some comment--> - some comment
root - None
child - Child 1
child - Child 2
another - Child 3
&#234;
1
2
3
# 通过elementTreeName.write(filePath)可以把树写进文件或通过url传输
# 或者如果你只有elementName的话,可以这么写:
# etree.ElementTree(elementName).write(filePath)
1
2
3
4
5
6
7
# 简单粗暴的直接写xml也可以
root = etree.XML(
'<html><head/><body><p>Hello<br/>World</p></body></html>')
# 通过method参数,可以序列化为不同格式,默认method = 'xml'
print(etree.tostring(root))
print(etree.tostring(root, method='html'))
print(etree.tostring(root, method='text'))
b'<html><head/><body><p>Hello<br/>World</p></body></html>'
b'<html><head></head><body><p>Hello<br>World</p></body></html>'
b'HelloWorld'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ElementTree class,是一个element的容器,提供了一些方法来对整个root node文档进行操作
root = etree.XML('''\
<?xml version="1.0"?>
<!DOCTYPE root SYSTEM "test" [ <!ENTITY tasty "parsnips"> ]>
<root>
<a>&tasty;</a>
</root>
''')

tree = etree.ElementTree(root)
print(tree.docinfo.xml_version)
print(tree.docinfo.doctype)

tree.docinfo.public_id = '-//W3C//DTD XHTML 1.0 Transitional//EN'
tree.docinfo.system_url = 'file://local.dtd'
print(tree.docinfo.doctype)
1.0
<!DOCTYPE root SYSTEM "test">
<!DOCTYPE root PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "file://local.dtd">
1
2
print(etree.tostring(tree))
print(etree.tostring(tree.getroot()))
b'<!DOCTYPE root PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "file://local.dtd" [\n<!ENTITY tasty "parsnips">\n]>\n<root>\n  <a>parsnips</a>\n</root>'
b'<root>\n  <a>parsnips</a>\n</root>'
1
2
3
4
5
6
# 解析文档中以string读入的xml
some_xml_data = "<root>data</root>"

# .fromstring函数其实和.XML差不多
root = etree.fromstring(some_xml_data)
print(etree.tostring(root))
b'<root>data</root>'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 二进制读取解析
from io import BytesIO

some_file_or_file_like_object = BytesIO(b"<root>data</root>")
tree = etree.parse(some_file_or_file_like_object)
print(etree.tostring(tree))
# 注意:parse函数返回的是ElementTree对象,不是Element,通过getroot可以转化为Element对象
# ElementTree和Element在某些方面相似,但是方法调用上不同,比如.tag只有Element可以使用
root = tree.getroot()
print(etree.tostring(root))
try:
print("tree执行:", tree.tag)
except:
print("root执行:", root.tag)
b'<root>data</root>'
b'<root>data</root>'
root执行: root
1
2
3
4
# parser对象
parser = etree.XMLParser(remove_blank_text=True)
root = etree.XML("<root> <a/> <b> </b> </root>", parser)
print(etree.tostring(root))
b'<root><a/><b>  </b></root>'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Incremental parsing增量解析,比如在网络分步传输的场景下需要使用
# 方法一
class DataSource:
data = [ b"<roo", b"t><", b"a/", b"><", b"/root>" ]
def read(self, requested_size):
try:
return self.data.pop(0)
except IndexError:
return b''

tree = etree.parse(DataSource())
print(etree.tostring(tree))

# 方法二
parser = etree.XMLParser()

parser.feed("<roo")
parser.feed("t><")
parser.feed("a/")
parser.feed("><")
parser.feed("/root>")

root = parser.close()
print(etree.tostring(root))
b'<root>data</root>'
b'<root><a/></root>'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Event-driven parsing事件驱动的解析,如只需要很大的树中的一小部分时使用
# 方法一
# iterparse默认只解析end事件
some_file_like = BytesIO(b"<root><a>data</a></root>")
for event, element in etree.iterparse(some_file_like):
print("%s, %4s, %s" % (event, element.tag, element.text))

print()

# 可以修改events参数解析所有事件
some_file_like = BytesIO(b"<root><a>data</a></root>")
for event, element in etree.iterparse(some_file_like,
events=("start", "end")):
print("%5s, %4s, %s" % (event, element.tag, element.text))
end,    a, data
end, root, None

start, root, None
start,    a, data
  end,    a, data
  end, root, None
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 方法二
class ParserTarget:
events = []
close_count = 0
def start(self, tag, attrib):
self.events.append(("start", tag, attrib))
def close(self):
events, self.events = self.events, []
self.close_count += 1
return events

parser_target = ParserTarget()

parser = etree.XMLParser(target=parser_target)
events = etree.fromstring('<root test="true"/>', parser)

print(parser_target.close_count)

for event in events:
print('event: %s - tag: %s' % (event[0], event[1]))
for attr, value in event[2].items():
print(' * %s = %s' % (attr, value))
1
event: start - tag: root
 * test = true


/Users/imonce/anaconda/lib/python3.6/site-packages/ipykernel_launcher.py:15: DeprecationWarning: inspect.getargspec() is deprecated, use inspect.signature() or inspect.getfullargspec()
  from ipykernel import kernelapp as app
1
2
3
4
events = etree.fromstring('<root test="true"/>', parser)
print(parser_target.close_count)
events = etree.fromstring('<root test="true"/>', parser)
print(parser_target.close_count)
2
3
1
2
3
4
for event in events:
print('event: %s - tag: %s' % (event[0], event[1]))
for attr, value in event[2].items():
print(' * %s = %s' % (attr, value))
event: start - tag: root
 * test = true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# namespace命名空间,格式为:{namespace_name}tag_name
# 一般是链接,空链接的话会 ns+编号 给一个代号
# a:b的tag名会直接报错
xhtml = etree.Element("{http://www.w3.org/1999/test}testhtml")
body = etree.SubElement(xhtml, "{http://www.w3.org/1999/test}tbody")
body.text = "Hello World"

print(etree.tostring(xhtml))

# 高贵的html
xhtml = etree.Element("{http://www.w3.org/1999/xhtml}html")
body = etree.SubElement(xhtml, "{http://www.w3.org/1999/xhtml}body")
body.text = "Hello World"

print(etree.tostring(xhtml))
b'<ns0:testhtml xmlns:ns0="http://www.w3.org/1999/test"><ns0:tbody>Hello World</ns0:tbody></ns0:testhtml>'
b'<html:html xmlns:html="http://www.w3.org/1999/xhtml"><html:body>Hello World</html:body></html:html>'
1
2
3
4
5
6
7
8
9
# 设置默认命名空间
XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml"
XHTML = "{%s}" % XHTML_NAMESPACE

NSMAP = {None : XHTML_NAMESPACE} # the default namespace (no prefix)
xhtml = etree.Element(XHTML + "html", nsmap=NSMAP) # lxml only!
body = etree.SubElement(xhtml, XHTML + "body")
body.text = "Hello World"
print(etree.tostring(xhtml))
b'<html xmlns="http://www.w3.org/1999/xhtml"><body>Hello World</body></html>'
1
2
3
4
5
6
7
8
9
# QName方法,快速合成或提取namespace
tag = etree.QName('http://www.w3.org/1999/xhtml', 'html')
print(tag.localname)
print(tag.namespace)
print(tag.text)

root = etree.Element('{http://www.w3.org/1999/xhtml}html')
tag = etree.QName(root)
print(tag.localname)
html
http://www.w3.org/1999/xhtml
{http://www.w3.org/1999/xhtml}html
html
1
2
# nsmap方法,直接提取命名空间字典
print(xhtml.nsmap)
{None: 'http://www.w3.org/1999/xhtml'}
1
2
3
4
5
6
# 子元素继承父元素命名空间
root = etree.Element('root', nsmap={'a': 'http://a.b/c'})
child = etree.SubElement(root, 'child',
nsmap={'b': 'http://b.c/d'})
print(root.nsmap)
print(child.nsmap)
{'a': 'http://a.b/c'}
{'b': 'http://b.c/d', 'a': 'http://a.b/c'}
1
2
3
4
5
6
# 添加带命名空间的属性,格式为:set({namespace_name}attr_name, attr_value)
body.set(XHTML + "bgcolor", "#CCFFAA")
print(etree.tostring(xhtml))
# 注意:get的时候也要带命名空间
print(body.get("bgcolor"))
print(body.get(XHTML+"bgcolor"))
b'<html xmlns="http://www.w3.org/1999/xhtml"><body xmlns:html="http://www.w3.org/1999/xhtml" html:bgcolor="#CCFFAA">Hello World</body></html>'
None
#CCFFAA
1
2
3
4
5
6
# 也可以用XPath
find_xhtml_body = etree.ETXPath( # lxml only !
"//{%s}body" % XHTML_NAMESPACE)
results = find_xhtml_body(xhtml)

print(results[0].tag)
{http://www.w3.org/1999/xhtml}body
1
2
# iter的话也要考虑namespace
for el in xhtml.iter('{*}body'): print(el.tag)
{http://www.w3.org/1999/xhtml}body
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# E-factory:提供一种简单紧凑的语法来生成XML和HTML
from lxml.builder import E

def CLASS(*args): # class 是python中的保留字,无法直接当做属性名
return {"class":' '.join(args)}

html = page = (
E.html( # create an Element called "html"
E.head(
E.title("This is a sample document")
),
E.body(
E.h1("Hello!", CLASS("title")),
E.p("This is a paragraph with ", E.b("bold"), " text in it!"),
E.p("This is another paragraph, with a", "\n ",
E.a("link", href="http://www.python.org"), "."),
E.p("Here are some reserved characters: <spam&egg>."),
etree.XML("<p>And finally an embedded XHTML fragment.</p>"),
)
)
)

print(str(etree.tostring(page, pretty_print=True),encoding='utf-8'))
<html>
  <head>
    <title>This is a sample document</title>
  </head>
  <body>
    <h1 class="title">Hello!</h1>
    <p>This is a paragraph with <b>bold</b> text in it!</p>
    <p>This is another paragraph, with a
      <a href="http://www.python.org">link</a>.</p>
    <p>Here are some reserved characters: &lt;spam&amp;egg&gt;.</p>
    <p>And finally an embedded XHTML fragment.</p>
  </body>
</html>
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
# 此外还有一种基于属性的方法
from lxml.builder import ElementMaker # lxml only !

E = ElementMaker(namespace="http://my.de/fault/namespace",
nsmap={'p' : "http://my.de/fault/namespace"})

DOC = E.doc
TITLE = E.title
SECTION = E.section
PAR = E.par

my_doc = DOC(
TITLE("The dog and the hog"),
SECTION(
TITLE("The dog", tType='title'),
PAR("Once upon a time, ..."),
PAR("And then ...")
),
SECTION(
TITLE("The hog"),
PAR("Sooner or later ...")
)
)

print(str(etree.tostring(my_doc, pretty_print=True),encoding='utf-8'))
<p:doc xmlns:p="http://my.de/fault/namespace">
  <p:title>The dog and the hog</p:title>
  <p:section>
    <p:title tType="title">The dog</p:title>
    <p:par>Once upon a time, ...</p:par>
    <p:par>And then ...</p:par>
  </p:section>
  <p:section>
    <p:title>The hog</p:title>
    <p:par>Sooner or later ...</p:par>
  </p:section>
</p:doc>
1
2
3
4
5
# XPath的一些例子
root = etree.XML("<root><a x='123'>aText<b/><c/><b/></a></root>")
# 找儿子(单层子节点)
print(root.find("b"))
print(root.find("a").tag)
None
a
1
2
# 找孩子(所有子节点)
print(root.find(".//b").tag)
b
1
2
# 带属性找孩子
print(root.findall(".//a[@x]")[0].tag)
a
1
2
3
4
5
6
# 通过ElementTree找路径
tree = etree.ElementTree(root)
a = root[0]
print(tree.getelementpath(a[0]))
print(tree.getelementpath(a[1]))
print(tree.getelementpath(a[2]))
a/b[1]
a/c
a/b[2]