复杂网络仿真从入门到精通0:学习路线

一、为什么要学习复杂网络仿真?

在科研中,复杂网络模型 已经成为分析系统结构与动态的重要工具。无论是社交网络、交通网络,还是生物分子网络,其核心问题往往都离不开一个:

“系统中的节点是如何相互影响的?”

很多同学在读论文时,会看到“BA模型”“WS模型”“网络鲁棒性仿真”等名词,但真正动手实现的时候往往卡在第一步——
不知道该从哪里学起,更不知道怎么建模、怎么画图、怎么计算指标。


二、入门前的准备:看书先了解一些基础概念

复杂网络的基础是图论。建议初学者先从一本书开始,从头看起,了解一些基础概念,然后再进行后续研究。

📘 推荐入门教材:《复杂网络理论及其应用》汪小帆 李翔 陈关荣. 清华大学出版社 2006

当然不是整本书都需要看,一开始只需要了解基础概念,下面我会给出一个推荐:

没太多时间看前三章就行,第一章引论介绍,第二章 网络拓扑基本模型及其性质,第三章
Internet拓扑特性及建模。重点是了解基础概念,指标定义,网络特性。

后面的章节基本就是对应了具体的研究方向,有时间的可以快速过一下:

第4章 复杂网络上的传播机理与动力学分析

第5章 复杂网络上的相继故障

第6章 复杂网络中的搜索

第7章 复杂网络中的社团结构

第8章 复杂网络中的同步

了解了这些基础概念之后,可以看论文有哪些自己感兴趣的方向,然后再针对看,研究就可以了。


三、仿真入门路线图(Matlab方向)

如果你对matlab这门语言不熟悉的话,建议学一下matlab软件基础操作,学习matlab语言基础,矩阵操作,常见数学计算。这一步只是初步学习,为能看懂别人的仿真打好基础。

注意:matlab的功能极为强大,有太多的功能是我们一时用不上的,最好是先了解基础,然后后面根据需要再进行学习,节省精力。(其他的语言也是同理)

如果你是科研方向的学生或者开发者,可以按下面这条路线循序渐进:

阶段1:网络结构基础

  • 了解随机网络(Erdős–Rényi)、小世界网络(Watts–Strogatz)和无标度网络(Barabási–Albert)的生成机制;
  • 用 Matlab 自带函数或自己写脚本生成网络;
  • 学会可视化:plot(G)

示例:生成小世界网络

1
2
3
n = 100; k = 4; p = 0.1;
G = watts_strogatz(n, k, p);
plot(G)

阶段2:网络指标分析

  • 计算平均度、聚类系数、路径长度、介数中心性;

  • 掌握 Matlab 的 centrality(G, 'betweenness')clustering_coef_bu(A) 等函数;

  • 对比不同模型的特性差异。

阶段3:按方向进行后续仿真

因为后续的方向不同会导致仿真之间也有很大的不同。


四、建议

  • 看书和仿真可以一起做
    先把基础指标和模型原理搞透,然后可以尝试仿真进行实现,一步一个脚印。

举个例子:你今天看到了度这个指标,那么这个指标的定义是什么?如何实现呢?

matlab中计算很简单:然后就涉及到了两个问题,A是怎么得到的?sum函数的原理是什么?解决了一个指标,等于理解了三个概念。

1
2
% A为邻接矩阵
degree = sum(A);
  • 做本阶段该做的事情

看论文,或者是看博客,发现哪个复杂网络概念不了解,那就去了解概念。

看别人的代码发现matlab(或者其他语言)看不明白,那就去查语句,查函数,查实现。

入门的时候就不要想着后续的研究模型,时候到了再进行研究就可以。

五、最后

欢迎关注,我会持续更新:复杂网络仿真从入门到精通。我会持续更新每一步的代码、科研思路和实现细节,希望能让更多科研人少走弯路。

资料分享:
分享地址

本系列对应的github:工程地址

matlab实现简单级联失效模型

级联失效的定义如下:网络中,一个或少数几个节点或连线的失效会通过节点之间的耦合关系引发其他节点也发生失效,进而产生级联效应,最终导致相当一部分节点甚至整个网络的崩溃,这种现象就称为级联失效,有时也形象称之为“ 雪崩”。

级联失效的原理并不难懂,但是实现起来并不简单,也曾在网上寻找过代码,可以几无所得。有的实现代码确实有点复杂,但我觉得从逻辑上来讲,应该是不难的。

负载-容量模型是应用最为广泛的级联失效模型。该模型主要有三个部分组成,初始负载定义、节点容量设置、负载重分配策略。
一个简单的负荷容量模型流程图可如下图所示:

流程图
重分配策略一般是创新点集中存在的地方,因为这块可以做文章的地方比较多,我理解重这部分可以分三块:策略,节点状态和分配负载的比例。
策略:常见的负载重分配策略有随机分配,按某指标进行分配,剩余负载重分配等等。
节点状态:最简单的就是两种:正常和失效。所以有研究者又设置了一种叫做 过载 的状态,顾名思义就是介于正常和失效的中间状态。
分配负载的比例:最简单的就是当节点失效的时候,失效节点上的所有负载都参与重分配。那么如果有过载这个节点状态呢?那么此时就不是所有的负载都会被分配。
💥 级联失效动态演示
下图是失效过程的动态模拟,红色节点表示在各阶段中失效的节点,示例网络是100节点的侧视图。

根据实际攻击结果的动态图
🎯 模拟前后网络对比

对比图

上面的理论说的差不多,具体从哪里开始创新还是要靠个人自己的研究,本文从代码层面帮助大家实现一个最基础的级联失效模型,希望可以帮助到大家。
下面的示例代码:初始负载为度,节点容量设置为线性模型设置,负载重分配策略为按照度分配。

其中初始负载L0 定义和节点容量C 设置,这两部分相对比较简单:

1
2
3
L0 = sum(A);
C = (1 + a)*L0
重分配部分:

%% 重分配
pre_attack_list = [1];
% 初始节点状态 一开始都是正常所以都是0, 失效为1
status_list = zeros([1 node_num]);
% 失效节点列表
node_index_list{1,1} = pre_attack_list;
% 当前失效的列表
failure_list = node_index_list{1,1};

status_list(node_index_list{1,1}) = 1;
% 存储每个阶段的失效列表
failure_cell{1,1} = node_index_list{1,1};

% 1.从node_index_list拿出此次失效的节点
% 2.计算出相邻节点开始分配
for i = 1:length(indexs)
% 开始分配
f1(i) = Load_redistribution(indexs(i),indexs, L0, status_list(node_index(j)));
L( indexs(i) ) = L0( indexs(i) ) + f1(i)* L0( node_index(j));

% 3. 更新节点状态
% 4. 更新失效列表

if status_list(indexs(i)) == 1
    failure_list = [failure_list indexs(i)];
end

end
% 5. 根据失效列表求得当前网络的指标
A(failure_list,:) = 0;
A(:,failure_list) = 0;
AA = A;

% 记载失效集合
failure_cell{1,k+1} = failure_list;
node_index_list{1,k+1} = setdiff(failure_cell{1,k+1}, failure_cell{1,k});

% 判断级联失效是否停止
all_the_indexs = figure_NetEff( AA ,node_num);
NetEff = all_the_indexs.Eglob;
Max_sub_num = all_the_indexs.Max_sub_num;
实现算法不易,本文仅放置了部分代码,若想获取完整代码或者与我交流,可关注:公众号【三紫智造局】,或直接访问下方内测入口也可以。后续会一直更新相关复杂网络算法和内容,欢迎关注。

放在最后:
我们最近正在开发一个复杂网络平台,参与内测后续上线会赠送免费计算次数
平台入口(内测中)
www.threepurple.cn/

并且我准备建立一个复杂网络交流群,想进群可以联系我。

如何使用postman进行API测试

最近接触了Postman API测试,业务上是需要写出来Postman脚本。以前可以说是从来都没有听说过脚本的事情,虽说也其实这个工具有一点了解,但是在这回实践之后才有了更多的理解。首先推荐大家学习一个Pluralsight的视频Postman Fundamentals,我只是有针对性的看了前一部分,就感觉收获良多。一个好的入门课程其实也是很重要的。

这里我就举例说一个spring security的用户验证的测试,其他的测试过程也是很相似的。

认证测试:

  • step1 在地址栏中输入自己API的地址(例子中是用户登陆认证的一个form)
  • step2 选择请求的方式,这个postman里面有很多,我这里选择的是POST
  • step3 在headers我们可以把Content-Type设置出来
  • step4 在body中加入需要请求的key和对应的value(这个例子中就是username、password,因为spring secutity的登陆认证机制只有验证登陆后才可以进行后续操作,否则就会报401未认证)
  • step5 在Tests中可以编写一些测试语句,以检查得到的Response body是否是你需要的

这个例子的Tests如下:

编写好测试界面中的测试语句,然后点击send,可以在下面的Tests Results中查看测试是否通过,例子中的就是测试全部通过了,如果有错误,下面的提示中会告知你哪个测试出错,以及为什么出错。

下面说一下这几个简单测试语句:

我想在开始之前提醒大家一件事,postman测试语句是需要很精准的,也就是说你写的测试语句需要可以精准的、直观的测试出返回的response是否符合预期,你的测试需要着眼于关键点。

状态码测试:

1
2
3
pm.test("Status code is 200", function () {
pm.response.to.have.status(200);
});

第一行中的”Status code is 200”是可以自己修改的,这一行字符串会在下面的控制台输出(可参照上图),这里建议要取一些有意义的字符串,可以见名知义那种。

第二行中的 200,就是你期望得到的状态码。

是否得到认证的测试:

第二行就是定义jsonData接受response中json数据,然后就是进行判断返回的字段是否是预期的。

1
2
3
4
pm.test("Your test user is authenticated", function () {
var jsonData = pm.response.json();
pm.expect(jsonData.authenticated).to.eql(true);
});

那么对比验证一下认证失败的情况:

我随便的设置了登录的用户名和密码,可以看到下方的Test results中3条都fail了,并且还有返回值的对比说明。

希望以上能对大家有所帮助。

如何在spring security中对用户密码进行加密

最近在实践中接触了spring security密码加密的相关内容。

以前在数据库中存入用户密码的时侯,选择的都是明文密码,没有任何的安全性,这种方式其实非常的危险。所以我们需要在用户注册时就对用户键入的密码进行加密,然后存储到数据库中。之后在用户登录的时候再对数据中的加密密码和用户键入的密码进行一个对比的验证。

这里不推荐使用spring security自带的PasswordEncoder方法进行加密,因为这样很容易被破解掉。推荐编写自己的加密方法,可以选择SHA-1,SHA-256等算法,然后也可以在密码加密之前中加入盐值salt。然后写自己的MyPasswordEncoder方法然后重写PasswordEncoder,实现encode,matches方法。

spring security自带的PasswordEncoder方法如下所示:

1
2
3
4
5
public interface PasswordEncoder {

String encode(CharSequence rawPassword);

boolean matches(CharSequence rawPassword, String encodedPassword);

我们可以看到这个PasswordEncoder接口有两个方法,encode的方法是加密,matches方法是比较初始密码加密后是否等于加密后的密码。

下面展示一个小的例子:新建自己的MyPasswordEncoder方法实现PasswordEncoder接口中的两个方法,这里我就写了一个encode的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyPasswordEncoder implements PasswordEncoder {

//加密算法
private static MyDigestAlgorithm myDigestAlgorithm;

String encodedPassword = "";
// add salt
String saltedPassword = Common.SALT + rawPassword.toString();
try {
//encrypt the password
encodedPassword = MyDigestAlgorithm.toHexString(MyAlgorithm.getSHA(saltedPassword));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}

return encodedPassword;
}
}
}

有了加密方法之后,就可以在service编写业务代码:简单来说就是,存入数据库之前把用户键入的密码加密,然后再存入数据库。例子如下所示:

1
2
3
4
5
6
public void addAccounts(Account account) {

account.setPassword(myPasswordEncoder.encode(account.getPassword()));
accountMapper.addAccounts(account);

}

因为我们是自己重写的PasswordEncoder方法,所以在spring security的config中,需要注入我们的加密类,否则登陆的时候就会认证失败。例子如下:

1
2
3
4
5
@Bean
public MyPasswordEncoder myPasswordEncoder(){

return new MyPasswordEncoder();
}

希望能对您有所帮助。

matlab运行速度优化

当我们需要处理较大数据量,或者程序中有一些较为复杂的逻辑。这个时候就需要对程序进行一定的优化,以下是一些小技巧与例子的展示。

硬件方面

1.提升电脑的配置

2.在Matlab软件中设置,分配更多的运行内存

代码方面:

  1. 数据格式double转成single

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    A_length = length(A);
    numbers = 50;
    for k = 1:numbers
    for i = 1:A_length
    for j = 1:A_length
    A(i,j) = A(i,j)*rand;
    end
    end
    end


    矩阵维度:1000*1000
    double:时间已过 1.700172 秒
    single:时间已过 1.546184 秒。

    矩阵维度:5000*5000
    double:时间已过 50.057981 秒。
    single:时间已过 45.139951 秒。
  1. 避免for循环

    code1:

    1
    2
    3
    4
    5
    6
    7
    y =[];
    number = 100000;
    for i =1:number
    y = [y sin(i)];
    end

    % 时间已过 2.749184 秒。

    code2:

    1
    2
    3
    4
    y =[];
    number = 100000;
    y = sin([1:1:number]);
    时间已过 0.001548 秒。
  1. 预先声明变量长度

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    number = 1000; %5000
    %%新建一个矩阵
    %这一句就是原先声明变量的长度
    A = zeros([number number]);

    for i =1:number
    for j =1:number
    if rand >0.5
    A(i,j) = 1;
    end
    end
    end


    1000*1000
    未声明:时间已过 0.237895 秒。
    已声明:时间已过 0.041581 秒。

    5000*5000
    未声明:时间已过 41.466290 秒。
    已声明:时间已过 0.962321 秒。

经过测试,可以看到预先分配数组的大小和避免使用for循环对运行速度的提升效果是非常好的,特别是当数据量越大的时候,效果也就越好。

希望对大家有所帮助。

解决centos7下报错:net/http: TLS handshake timeout

最近在使用CentOS 7按照官方网站的文档安装docker时遇到了一个问题:

当我运行这一句时

1
sudo docker run hello-world

出现了bug:

1
net/http: TLS handshake timeout

解决方案:

配置镜像,修改 /etc/docker/daemon.json 文件,如果没有可以新建一个文件,然后加入以下的docker镜像地址:

1
2
3
4
5
6
{
"registry-mirrors": [
"https://dockerhub.azk8s.cn",
"https://hub-mirror.c.163.com"
]
}

保存退出
然后重启docker

1
sudo systemctl restart docker

这里可以检查一下配置的镜像是否生效:

1
docker info

如果显示的配置信息的末尾部分出现如下字样,说明配置完成:

镜像字样示例

再次运行:

1
sudo docker run hello-world

出现以下的信息,证明问题已经解决了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/

For more examples and ideas, visit:
https://docs.docker.com/get-started/

如何对复杂网络建模所需要的数据进行预处理

上一篇文章介绍了如何构建Space L实体网络的模型,这一篇是对上一篇文章的一个补充优化。

以下部分摘自上一篇文章:如何建立复杂网络实体网络的Space L模型

地铁网络,一般都有三四百个节点,线路十几条左右,看地铁图的是一个眼花缭乱。若是人工统计出来数据也是一项大工程。看着就想放弃,但其实掌握一定的方法并没有那么的费劲。

  1. 按线路进行节点的统计,先编号,然后去除掉重合的节点
  2. 统计连接关系时有一定的规则:比如从左往右统计、从上往下统计,这样可以避免重复统计
  3. 不要直接列出邻接矩阵,先统计出连接关系生成邻接表,然后再转成邻接矩阵
  4. 关于邻接表,最好再检查一遍
  5. 以上工作最好分成数天进行,否则负荷工作效率低且出错率较高

可以看到,建模时候最头疼的就是数据的处理问题,运用以上的经验可以提升我们的效率,但是治标不治本,依旧会浪费掉我们大量的时间。其实,如果不考虑换乘站(重复节点),连接关系还是比较好统计的,比如一条线路有10个站点,按顺序分别为a、b、c….j,那么连接关系可以表示为下图的1-9列:

线路转化邻接表

最近有一个需求,要统计某市的公交网络,有300多条线路,大概有3000多节点。如果此时还按之前的办法:人工统计线路中的站点,然后进行编号的话,那整个工程量不仅巨大,并且在统计过程中也很容易出错。

所以可以让程序帮助我们去识别站点名称,然后依次给它们编号,这样就可以生成直接使用邻接表。

具体处理方式,可以大概分为以下几个步骤

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
% 1.从xls文件中,读取数据(或者直接新建数据)
% rawDataNum是读取到的数值,可以是权重,数据类型:double
% rawDataStr是读取到的字符串,是邻接表,数据类型:cell

% 2.用b接收rawDataStr中的所有不重复的字符串,数据类型 cell

% 3.因为cell类型矩阵中存储的是字符串数据,不好处理
% 所以需要把b和rawDataStr转换为string数组b_str和raw_str
% 注意这里可以检查一下b_str中的字符串

%4.进行数据处理
test = [];

for i = 1:length(b_str)
for j = 1:length(raw_str)
%判断条件
if(raw_str(j,1) == b_str(i,1))
test(j,1) = i;
end


%判断条件
if(raw_str(j,2) == b_str(i,1))
test(j,2) = i;
end

end
end
%这时就可以得到邻接表test

%判断是否为无权网络,判断标准rawDataNum是否为空,这与你的初始数据有关
if(length(rawDataNum) ~= 0)
test = [test rawDataNum];
else
disp('无权网络')
end

%这一步就是把邻接表test直接转换为邻接矩阵A,
%可以参照:如何建立复杂网络实体网络的Space L模型中的函数
A = ainc2adj( test );
% 判断是否有孤立节点
if(length(find(~sum(A))))
disp('存在孤立节点')
end

测试邻接表:

结果:

邻接表

节点编号与名称对应关系:

可以看到效果还是不错的,而且不仅限于交通网络。对于一些较大型的实体网络,比如作者合作网络、社交网络,应该也会有不错的效果。欢迎大家与我进行交流,

该文章首发于:XuXing’s blog

复杂网络相关内容可以访问:复杂网络

如何建立复杂网络实体网络的Space L模型

复杂网络是一个非常庞大的研究领域,有众多研究方法与研究对象,社交网络、科学家网络、生物网络、交通网络、生物网络等等。在进行仿真时候,有的网络过于庞大无法用实际的数据进行仿真,例如社交网络。而有一些网络规模较小,就需要用实际的数据进行仿真了,例如交通网络。

那么无论网络规模大小,对这些网络进行研究的时候,第一步往往是建模,只有模型建好了后续的研究、仿真才好进行下去。建模后,可以对网络指标进行分析,可以分析网络的抗毁性等等。总之,建模总是第一步的。

下面我就分享一下,自己对于复杂网络中实体网络建模的一些经验,以地铁网络为例:

建模方法,一般有Space L、Space P、Space B、Space C法,比较常用的建模规则是Space L法。

地铁网络,一般都有三四百个节点,线路十几条左右,看地铁图的是一个眼花缭乱。若是人工统计出来数据也是一项大工程。看着就想放弃,但其实掌握一定的方法并没有那么的费劲。

  1. 按线路进行节点的统计,先编号,然后去除掉重合的节点
  2. 统计连接关系时有一定的规则:比如从左往右统计、从上往下统计,这样可以避免重复统计
  3. 不要直接列出邻接矩阵,先统计出连接关系生成邻接表,然后再转成邻接矩阵
  4. 关于邻接表,最好再检查一遍
  5. 以上工作最好分成数天进行,否则负荷工作效率低且出错率较高

下面给出 邻接表 转成 邻接矩阵的matlab函数代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function b  = ainc2adj( x )
% x为邻接表(可在工作区中新建数据),输出的b为邻接矩阵
% 此函数是通过邻接表生成临接矩阵的
if min(x(:))==0;
x=x+1;
end
d=length(x);
a=max(max(x));
b=zeros(a,a);

for i=1:d
if x(i,1)==x(i,2);
b(x(i,1),x(i,2))=0;
else
b(x(i,1),x(i,2))=1;
b(x(i,2),x(i,1))=1;
end
end

一般来讲生成邻接矩阵,我们就得到了实体网络的节点连接情况。但我们都知道,人工统计的难免会有一些错漏,而这个时候需要用Gephi软件帮助我们进行进一步的检验。关于Gephi如何导入数据,可以参考这一篇文章。导入之后,你会得到一张拓扑图,如下图所示:

但是这样的图,我们没有办法看出网络可能存有什么问题,需要进一步的操作。

点击布局中的,选择一个布局。选择Force Atlas,选择运行,图会发生变化:

我们会发现红圈部分的点与整体网络没有任何联系,而交通网络是一个整体,那就说明这几个节点的连接关系一定都有问题。那么如何查看这几个点是哪几个点呢?点击预览,然后打开显示标签,最后点下方的刷新,就显示出来了节点标号。

然后可以用鼠标滚轮放大,调节左侧的节点编号显示的颜色,就可以大概看出来是那些节点出现问题了。然后回去对着连接图和节点编号去找就可以了

这个软件生成的图也是非常漂亮的,大家可以试一试,节点颜色、大小,边的颜色、大小,都可以自己调节,还有很多计算网络指标的功能。那么这次的分享就到这里,感谢大家的时间。

欢迎大家与我交流。

matlab实现随机攻击网络边+蓄意攻击网络连边(3)

其实在前面已经介绍过随机进攻节点和蓄意进攻节点的原理,今天和大家说一下边攻击。其实原理都是类似的,只要改动之前的一些代码就可以完成这个操作的。如果没有看过前两篇文章,那么建议你先看一下,有助于理解原理。

前两篇地址:

matlab实现随机攻击网络节点+蓄意攻击网络节点(1)附github完整工程地址

matlab实现随机攻击网络节点+蓄意攻击网络节点(2)

我们首先应该了解删除连边和删除的节点的区别:

删除节点:删除该节点及与该节点所有相连的边。

删除连边:只删除该连边,而不改变节点的状态。

如果结合维度为N*N邻接矩阵A来说:

删除节点a:要删除A(a,:)和A(:,a)这一列一行,矩阵A变为(N-1)*(N-1)维度

删除节点a和节点b的连边:A(a,b) = 0;A(b,a) = 0;(邻接矩阵的对称性),矩阵A还是N*N维度。

知道了具体逻辑,那么下面就是代码实现了:

之前我们删除节点的代码片段为:

1
2
3
4
5
6
7
8
9
10
for i = 1:numDelete
A( Name_Struct.Node_Key_Degree(i),: ) = 0; %% 删除节点 Node_Key_Degree(i),用 0 占位,不能置空
A( :,Name_Struct.Node_Key_Degree(i) ) = 0;
AA = A;
AA( sum(A)==0,: ) = [];
AA( :,sum(A)==0 ) = [];
Con_Index_NetEff = testEglob( AA );

Eglob(i) = Con_Index_NetEff.Net_Eff_Mymod;
end

边攻击需要的数据准备:

邻接表:也就是网络中的所有连边关系 也就是下面的代码片段中的 biao(2*edge_number的矩阵)
边的排序关系:(可以是随机排序,可以是按某种参数进行的排序)下面的代码片段中的Node_edge_bet(1*edge_number的矩阵),就是根据边介数参数得到的边的排序

那么改动后的边攻击代码片段为:

1
2
3
4
5
6
7
8
9
10
11
A = A_Init;          %% 网络邻接矩阵 A
% numDelete:删除连边的数量
for i = 1:numDelete
%把要删除的边置为0
A( biao(1,Node_edge_bet(i)),biao(2,Node_edge_bet(i)) ) = 0;
%把对称位置的边也置为0
A( biao(2,Node_edge_bet(i)),biao(1,Node_edge_bet(i)) ) = 0;
AA = A;
Con_Index_NetEff = testEglob( AA );
Eglob_edge_bet(i) = Con_Index_NetEff.Net_Eff;
end

经过分析之后,我们可以得到一个推测或者说是结论:那就是边攻击每次只攻击一个边,对网络的影响较小(相对于节点攻击来说)。

感谢大家的时间,并希望以上的内容会对大家有所帮助。

Veirlog学习记录-6-数字频率计的设计与实现(附完整工程)

FPGA的课程的大作业,我们选的是数字频率计设计。下面分享一下代码,还有工程文件,还有自己写的论文(里面会有更加详细的介绍),希望可以对你有所启发。

工程文件github地址

开发环境:Vivado 2015.4+Modelsim(仅用于仿真波形)
开发板:赛灵思公司 xc7a100tcsg324-1

总体设计要求:

  • 可测量脉冲信号的频率
  • 被测信号由100MHz的系统时钟分频获得,频率为学号*1000 Hz
  • 测量结果在6位数码管上显示,高位若是零则不显示该位
  • 采用连续测量方式,每4秒为1个周期,其中1秒用于测量,3秒用于显示

总体设计框图:

子模块设计:

- 分频模块:

结构图:
代码如下:

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
module div(
input clk_100mhz, //系统给定的时钟频率
output clk_190hz, //数码管的动态扫描频率
output reg clk_1Hz, //控制模块的驱动频率
output reg fin //输出待测试信号的频率
);
reg [9:0] cnt0;
reg [30:0] cnt;
reg [18:0] cnt1;

always @(posedge clk_100mhz)
cnt1 = cnt1 + 1;
assign clk_190hz = cnt1[18];
always @(posedge clk_100mhz)
if(cnt == 50000000) begin
cnt = 0;
clk_1Hz = ~clk_1Hz;
end
else
cnt = cnt + 1;
always @(posedge clk_100mhz)
if(cnt0 == 499) begin //生成的测试信号的频率为100KHz
cnt0 = 0;
fin = ~fin;
end
else
cnt0 = cnt0 + 1;

endmodule

被测频率本来要求是学号*1000Hz,但是我的学号无法被整除,所以无法度量频率计的的精度,更无法进行误差分析。所以才选取了100KHz作为测试频率。

- 控制模块

结构图:

code:

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
module control(clk_1Hz, rst, count_en, latch_en, clear);  
input clk_1Hz;
input rst; //复位信号
output count_en; //计数使能
output latch_en; //锁存使能
output clear; //清零信号
reg [2:0] state; //状态信号,用于控制各种使能信号
reg count_en;
reg latch_en;
reg clear;

always @(posedge clk_1Hz or negedge rst)
if(!rst) //复位信号有效
begin //各种使能信号清零
state <= 3'b000;
count_en <= 1'b0;
latch_en <=1'b0;
clear <= 1'b0;
end
else //遇到基准信号的下一个上升沿,状态变化一次,每次变化后状态持续1s
begin
case(state)
3'b000:
begin //第一个上升沿到达,开始计数,计数1个基准信号周期内待测信号的上升沿个数,此个数即为待测信号的频率
count_en <= 1'b1; //计数使能信号有效
latch_en <= 1'b0;
clear <= 1'b0;
state <= 3'b001;
end
3'b001:
begin //第二个上升沿到达,计数完成,锁存使能信号有效,测得频率锁存至锁存器中
count_en <= 1'b0;
latch_en <=1'b1;
clear <= 1'b0;
state <= 3'b010;
end
3'b010:
begin //第三个上升沿到达,计数完成,锁存使能信号有效,测得频率锁存至锁存器中
count_en <= 1'b0;
latch_en <=1'b1;
clear <= 1'b0;
state <= 3'b011;
end
3'b011:
begin //第四个上升沿到达,计数完成,锁存使能信号有效,测得频率锁存至锁存器中
count_en <= 1'b0;
latch_en <=1'b1;
clear <= 1'b0;
state <= 3'b100;
end
3'b100:
begin //第五个上升沿到达,清零使能信号有效,计数器清零,为下一次计数做准备
count_en <= 1'b0;
clear <= 1'b1;
latch_en <=1'b1;
state <= 3'b000; //状态清零,进入下一次测量
end
default:
begin
count_en <= 1'b0;
latch_en <=1'b0;
clear <= 1'b0;
state <= 3'b000;
end
endcase
end

1
endmodule

- 计数模块

结构图:

code:

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
module counter_10(en_in, rst, clear, fin, en_out, q);  
input en_in; //输入使能信号
input rst; //复位信号
input clear; //清零信号
input fin; //待测信号
output en_out; //输出使能,用于控制下一个计数器的状态,当输出使能有效时,下一个模10计数器计数加1
output [3:0] q; //计数器的输出,4位BCD码输出

reg en_out;
reg [3:0] q;

always@ (posedge fin or negedge rst) //输入待测信号的上升沿作为敏感信号
if(!rst) //复位信号有效,计数器输出清零
begin
en_out <= 1'b0;
q <= 4'b0;
end
else if(en_in) //进位输入使能信号有效
begin
if(q == 4'b1001) //若q = 4'b1001的话,q清零,同时进位输出使能有效,即en_out 赋值为1'b1
begin
q <= 4'b0;
en_out <= 1'b1;
end
else //若q未达到4'b1001时,每到达待测信号的一个上升沿,q加1,同时输出进位清零
begin
q <= q + 1'b1;
en_out <=1'b0;
end
end
else if(clear) //若清零信号有效,计数器清零,用于为下一次测量准备
begin
q <= 4'b0;
en_out <= 1'b0;
end
else
begin
q <= q;
en_out <=1'b0;
end

endmodule

锁存模块

结构图:

code:

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
module latch(clk_1Hz, latch_en, rst, q0, q1, q2, q3, q4, q5, q6, q7,  
d0, d1, d2, d3, d4, d5, d6, d7);
input clk_1Hz, latch_en, rst;
input[3:0] q0, q1, q2, q3, q4, q5, q6, q7;
output[3:0] d0, d1, d2, d3, d4, d5, d6, d7;
reg[3:0] d0, d1, d2, d3, d4, d5, d6, d7;
always@ (posedge clk_1Hz or negedge rst)
if(!rst) //复位信号有效时输出清零
begin
d0 <= 4'b0; d1 <= 4'b0; d2 <= 4'b0; d3 <= 4'b0; d4 <= 4'b0;
d5 <= 4'b0; d6 <= 4'b0; d7 <= 4'b0;
end
else if(latch_en) //锁存信号有效时,将计数器的输出信号锁存至锁存器
begin
d0 <= q0; d1 <= q1; d2 <= q2; d3 <= q3; d4 <= q4;
d5 <= q5; d6 <= q6; d7 <= q7;
end
else //上面两种情况均未发生时,输入不变
begin
d0 <= d0; d1 <= d1; d2 <= d2; d3 <= d3; d4 <= d4;
d5 <= d5; d6 <= d6; d7 <= d7;
end

endmodule

显示模块

结构图:

code:

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
module IP_seg_disp(
input clk_190hz,
input [3:0] d0,d1,d2,d3,d4,d5,d6,d7
output reg [7:0] duan,
output reg [7:0] wei
);

reg [3:0] disp;
reg [2:0] smg_ctl;

always @ ( posedge clk_190hz)
smg_ctl = smg_ctl + 1'b1;
always @ (*)
case ( smg_ctl )
3'b000:begin
wei = 8'b11111110;
disp = d0 [3:0];
end
3'b001:begin
wei = 8'b11111101;
disp = d1 [3:0];
end
3'b010:begin
wei = 8'b11111011;
disp = d2 [3:0];
end
3'b011:begin
wei = 8'b11110111;
disp = d3 [3:0];
end
3'b100:begin
wei = 8'b11101111;
disp = d4 [3:0];
end
3'b101:begin
wei = 8'b11011111;
disp = d5 [3:0];
end
3'b110:begin
wei = 8'b10111111;
disp = d6 [3:0];
if( disp==0 ) //如果高位数值为0,则不显示该位
disp <=4'b1111;
end
3'b111:begin
wei = 8'b01111111;
disp = d7 [3:0];
if( disp==0 )
disp <=4'b1111
end
default:;
endcase

always @ ( * )
case (disp)
4'b0000:duan = 8'b11000000;
4'b0001:duan = 8'b11111001;
4'b0010:duan = 8'b10100100;
4'b0011:duan = 8'b10110000;
4'b0100:duan = 8'b10011001;
4'b0101:duan = 8'b10010010;
4'b0110:duan = 8'b10000010;
4'b0111:duan = 8'b11111000;
4'b1000:duan = 8'b10000000;
4'b1001:duan = 8'b10010000;
4'b1010:duan = 8'b10001000;
4'b1011:duan = 8'b10000011;
4'b1100:duan = 8'b11000110;
4'b1101:duan = 8'b10100001;
4'b1110:duan = 8'b10000110;
4'b1111:duan = 8'b11111111;
default:duan = 8'b11000000;
endcase
endmodule

顶层模块

code:

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
module freDetect(clk_100mhz,rst,duan,wei);  
input clk_100mhz;
input rst; //复位信号
output [7:0] duan;
output [7:0] wei;
wire[3:0] q0, q1, q2, q3, q4, q5, q6, q7; //中间数据
wire[3:0] d0, d1, d2, d3, d4, d5, d6, d7;
wire fin;
wire clk_1Hz;
wire clk_190hz;
//分频模块实例
div u_div(
.clk_1Hz(clk_1Hz),
.clk_190hz(clk_190hz),
.clk_100mhz(clk_100mhz),
.fin(fin)
);
//显示模块实例
IP_seg_disp u_IP_seg_disp( .clk_190hz(clk_190hz), .d0(d0),
.d1(d1), .d2(d2), .d3(d3), .d4(d4),
.d5(d5), .d6(d6), .d7(d7),
.duan(duan), .wei(wei)
);
//控制模块实例
control u_control(.clk_1Hz(clk_1Hz), .rst(rst), .count_en(count_en),
.latch_en(latch_en), .clear(clear));

//计数器模块实例
counter_10 counter0(.en_in(count_en), .clear(clear), .rst(rst),
.fin(fin), .en_out(en_out0), .q(q0));
counter_10 counter1(.en_in(en_out0), .clear(clear), .rst(rst),
.fin(fin), .en_out(en_out1), .q(q1));
counter_10 counter2(.en_in(en_out1), .clear(clear), .rst(rst),
.fin(fin), .en_out(en_out2), .q(q2));
counter_10 counter3(.en_in(en_out2), .clear(clear), .rst(rst),
.fin(fin), .en_out(en_out3), .q(q3));
counter_10 counter4(.en_in(en_out3), .clear(clear), .rst(rst),
.fin(fin), .en_out(en_out4), .q(q4));
counter_10 counter5(.en_in(en_out4), .clear(clear), .rst(rst),
.fin(fin), .en_out(en_out5), .q(q5));
counter_10 counter6(.en_in(en_out5), .clear(clear), .rst(rst),
.fin(fin), .en_out(en_out6), .q(q6));
counter_10 counter7(.en_in(en_out6), .clear(clear), .rst(rst),
.fin(fin), .en_out(en_out7), .q(q7));

//锁存器模块实例
latch u_latch(.clk_1Hz(clk_1Hz), .rst(rst), .latch_en(latch_en),
.q0(q0), .q1(q1), .q2(q2), .q3(q3), .q4(q4), .q5(q5),
.q6(q6), .q7(q7), .d0(d0), .d1(d1), .d2(d2), .d3(d3),
.d4(d4), .d5(d5), .d6(d6), .d7(d7));

endmodule

测试文件:

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
module freDetect_tb;  
parameter CLK_1HZ_DELAY = 50000000; //用于生成1Hz基准信号
parameter FIN_DELAY = 500; //用于生成100KHz待测信号
reg clk_1Hz;
reg fin;
reg rst; //复位
wire[3:0] d0, d1, d2, d3, d4, d5, d6, d7;
initial
begin
rst =1'b0;
#1 rst = 1'b1;
end
initial
begin
fin = 1'b0;
forever
#FIN_DELAY fin = ~fin;
end
initial
begin
clk_1Hz = 1'b0;
forever
#CLK_1HZ_DELAY clk_1Hz = ~clk_1Hz;
end
freDetect freDetect1(.clk_1Hz(clk_1Hz), .rst(rst), .fin(fin),
.d0(d0), .d1(d1), .d2(d2), .d3(d3), .d4(d4), .d5(d5), .d6(d6), .d7(d7));
endmodule

波形仿真结果如图所示:
结果显示
实验结果:

两张图片分别为 测量计数时和复位时和显示计数时:
测量计数时和复位时的显示显示计数时

我也写了几篇关于Veirlog的文章,感兴趣的同学可以去看看。该模块链接如下:
Verilog学习

© 2025 Three purple's blog All Rights Reserved. 本站总访客数 加载中... 人 | 本站总访问量 加载中...
Theme by hiero