逻辑表达式词法分析器分析结果的显示


      上篇博客文章《简易逻辑表达式词法分析器》讲到设计了一个逻辑表达式词法分析器,这次以那个词法分析器作为后端,利用VC搭建前端界面,展示一下分析效果。

      分析结果以树的形式表示:同一级节点表示具有相同的等级,参数比它的谓词的等级要高,比如Missile(x)中Missile等级为0级,x为1级。分析之后各个节点存储在Parser.list中,可以根据该链表的节点的level属性将各节点插入树种的相应位置。
     分析以上逻辑表达式可知,各个节点的等级如下:
      Missile  x  AND  Owns  Nono  Sub  x  =>  Sells  West  x  Nono
         0         1   0         0           1         1    2  0      0          1      1      1
     可见,利用堆栈来辅助将节点插入树中是最理想的方法。思路如下:将树的TVI_ROOT压栈;若当前节点的等级大于其前一个节点,将前一个节点压栈;若当前节点等级小于前一个节点,出栈前一节点等级 和  当前节点等级之差次;若当前节点等级等于前一节点等级,既不压栈也不出栈。每个当前节点的父节点为栈顶元素。

      源代码如下:

void CPPageFaultLogicExpression::OnBnClickedButtonParse()
{
    
// TODO: 在此添加控件通知处理程序代码
    UpdateData(TRUE);

    
if(m_strLogicExpression==L"")
    
{
        MessageBox(L
"请输入逻辑表达式!");
    }

    
else
    
{
        m_parser.Parse((wstring)m_strLogicExpression); 
// 进行词法分析
        m_bIsParsed=TRUE;

        
//// 向词法分析树种添加分析结果    ////
        m_tLexer.DeleteAllItems(); // 清除所有节点

        NodeList_It it
=m_parser.list.begin();
        NodeList_It tmp_it
=it;
        wchar_t tmp[
128];
        wcscpy(tmp,it
->content.c_str());

        stack
<HTREEITEM> st;    // 定义堆栈

        TVINSERTSTRUCT tvInsert;
        tvInsert.hParent 
= TVI_ROOT;
        tvInsert.hInsertAfter 
= TVI_LAST;
        tvInsert.item.mask 
= TVIF_TEXT;
        tvInsert.item.pszText 
= tmp;

        HTREEITEM hcur
=TVI_ROOT;
        st.push(hcur);
        hcur
=m_tLexer.InsertItem(&tvInsert); // 插入根节点
        
        HTREEITEM hprev;
        hprev
=hcur;
//        int level=0,max_level=0;

        it
++;

        
for(it;it!=m_parser.list.end();it++)
        
{
            wcscpy(tmp,it
->content.c_str());

            
if(it->level > tmp_it->level)    // 当前节点深度大于前一节点时,前一节点所在的树的位置入栈
            {
                st.push(hprev);
                hcur
=m_tLexer.InsertItem(tmp,hprev,TVI_LAST); // 插入新的节点
                
            }

            
else if(it->level < tmp_it->level) // 当前节点深度小于前一节点时,根据两节点深度之差将保存的节点出栈
            {
                
int level=tmp_it->level;
                
while(it->level < level--)
                
{
                    st.pop();
                }

                hprev
=st.top();
                hcur
=m_tLexer.InsertItem(tmp,hprev,TVI_LAST); // 插入新的节点
            }

            
else if(it->level == tmp_it->level) // 当前节点深度等于前一节点时,根据栈顶元素插入新节点
            {
                hprev
=st.top();
                hcur
=m_tLexer.InsertItem(tmp,hprev,TVI_LAST);
            }


            hprev
=hcur;
            tmp_it
=it;
        }


        UpdateData(FALSE);
    }
// end of else
}

   PS1:  当初考虑到中文的支持,利用wstring定义节点内容,现在发现是个错误,在字符转换的过程中很是麻烦,耽误了不少时间,走了不少弯路。
  PS2: 也许因为我对STL不是很熟悉,感觉比较麻烦,不知道如何被C++标准委员会接纳的?

利用VC设计简易逻辑表达式词法分析器

简易逻辑表达式词法分析器

待分析的逻辑表达式如下:

                EXISTS(x)(Missile(x) AND Owns(Father(Nono),Part_of(x))) => Sells(West,x,Nono)

为了处理逻辑表达式,必须首先提取出逻辑表达式的各个部分:关键字(AND, OR, NOT, FORALL, EXISTS, =>)、谓词和参数,同时必须对于表达式中多余的空格必须删除。这项工作由一个词法分析器完成。

该词法分析器最基础的部分是一个自定义节点Node,定义如下:

class Node
{
public:
    
// VARIABLEs
    enum NodeType {KEYWORD, PREDICATE, PARAM};    // Types of the node.
    static wstring KeyWord[];
    NodeType type;
    wstring content;    
// content of the node.
    int level;    // Record the level of the node,for example: 
                
// Owns(Nono,Sub(x))---"Owns" in level 0,"Nono""Sub" in level 1 and x in level 2.

    
// FUNCTIONs
    Node();
    Node(wstring con,NodeType type_
=KEYWORD,int lev=0);
    
~Node();
    
virtual Node& operator=(Node&); // Overrides of operator=

protected:

}
;

Node主要有三个部分:该节点内容、节点类型、节点的等级(用以对提取出的表达式进行进一步的分析)。

同时定义了一个链表:

typedef list<Node> NodeList; // Define List.

该链表用于存储整个逻辑表达式。

同时定义了一个类Parser,封装了NodeList用于对逻辑表达式进行操作。

class Parser
{
public:
    
// VARIABLEs
    NodeList list;
    Node
* node;
    
    
// FUNCTIONs
    Parser(){ node=NULL;};
    
~Parser();
    Node
* Parse(wstring str,int cur_pos=0); // function to parse a string.

private:

}
;

词法分析器的核心函数就是Node* Parser::Parse(wstring str,int cur_pos)该函数主要针对逻辑表达式中的'('' '','以及')'进行分析,流程图如下:

运行结果如下:

词法分析器运行结果

 
分析结果以树的形式表示:同一级节点表示具有相同的等级,参数比它的谓词的等级要高,比如Missile(x)中Missile等级为0级,x为1级。分
析之后各个节点存储在Parser.list中,可以根据该链表的节点的level属性将各节点插入树种的相应位置。
     分析以上逻辑表达式可知,各个节点的等级如下:
      Missile  x  AND  Owns  Nono  Sub  x  =>  Sells  West  x  Nono
         0         1   0         0           1         1    2  0      0          1      1      1
    
可见,利用堆栈来辅助将节点插入树中是最理想的方法。思路如下:将树的TVI_ROOT压栈;若当前节点的等级大于其前一个节点,将前一个节点压栈;若当
前节点等级小于前一个节点,出栈前一节点等级 和 
当前节点等级之差次;若当前节点等级等于前一节点等级,既不压栈也不出栈。每个当前节点的父节点为栈顶元素。


源代码如下:

//////////////////////////////////////////////////
//    
//    Function Name: Parse
//    Input:    a wstring(must), 
//            the start position of being parsed(choiced).
//    Output: a parsed list's head.
//
//////////////////////////////////////////////////
Node* Parser::Parse(wstring str,int cur_pos)
{
//    int start=cur_pos;
    Node* tmpNode=NULL;
    wchar_t pstr[
40]=L"";    // temp var to record words.
    int k=0// used together with pstr.
    int level=0;
    
int space_pos=0;
    
    
// Deal with the first keywords of a propositional fuction.
    
// Such as NOT, EXISTS(x), FORALL(y) and so on.
    
// EXAMPLE: EXISTS(x)(Missile(x) AND Owns(Nono,x)) => Sells(West,x,Nono)
    
// EXAMPLE: NOT(Missile(x)) AND Owns(Nono,Sub(x)) => Sells(West,x,Nono)
    
// EXAMPLE: Missile(x) AND Owns(Nono,Sub(x)) => Sells(West,x,Nono)
    while(cur_pos!=str.length())
    
{
        
if(str[cur_pos]==L'(')    // Words before '(' only can be KEYWORD or PREDICATE.
        {
            
bool isKeyWord=false;
            
for(int i=0;i<sizeof(Node::KEYWORD);++i)
            
{
                
if(!wcscmp(pstr,Node::KeyWord[i].c_str())) //pstr==Node::KeyWord[i]
                {
                    isKeyWord
=true;
                    tmpNode
=new Node(pstr,Node::KEYWORD,level);
                    list.push_back(
*tmpNode);    // Insert keyword.
                    break;
                }

            }

            
if(!isKeyWord && wcscmp(pstr,L"")) //pstr is not keyword or NULL
            {
                tmpNode
=new Node(pstr,Node::PREDICATE,level);
                list.push_back(
*tmpNode);    // Insert PREDICATE.
            }

            
++level;
            
++cur_pos;
            k
=0;
        }

        
else if(str[cur_pos]==L' ')
        
{
            
if(str[cur_pos-1]!=L')'// if a ' ' follows ')',then compare the following to or three with keyword.
            {
                
if(wcscmp(pstr,L"")) // pstr is not empty 
                {
                    wstring strAND(str,cur_pos
-3,3);
                    wstring strOR(str,cur_pos
-2,2);
                    
if(!strAND.compare(L"AND"|| !strOR.compare(L"OR"|| !strOR.compare(L"=>"))
                    
{
                        tmpNode
=new Node(pstr,Node::KEYWORD,level);
                        list.push_back(
*tmpNode);    // Insert keyword.
                        ++cur_pos;
                        k
=0;

                        memset(pstr,L
'',sizeof(pstr));
                        
continue;
                    }

                    
else 
                        exit(
0);    // Error!
                }

            }

                
            
++cur_pos;    // Ignore whitespace.
            continue;
        }

        
else if(str[cur_pos]==L','// pstr must be a param
        {
            
if(!wcscmp(pstr,L""&& str[cur_pos-1]==L')'// In a case like: Owns(Father(Nono),Sub(x))
            {
                
++cur_pos;
            }

            
else if(wcscmp(pstr,L"")) // pstr is not empty 
            {
                
if(str[cur_pos-1]==L')'// Something between a ')' and a ',', Error!
                    exit(1);
                
else
                
{
                    tmpNode
=new Node(pstr,Node::PARAM,level);
                    list.push_back(
*tmpNode);    // Insert PARAM.
                    ++cur_pos;
                }
    
            }
        

            k
=0;
        }

        
else if(str[cur_pos]==L')')    
        
{
            
if(!wcscmp(pstr,L""&& str[cur_pos-1]==L')'// In a case like: Owns(Father(Nono),Sub(x))
            {
                
++cur_pos;
            }

            
else if(wcscmp(pstr,L"")) //if pstr is not empty
            {
                
if(str[cur_pos-1]==L')')    // Error!
                    exit(1);
                
else
                
{
                    tmpNode
=new Node(pstr,Node::PARAM,level);
                    list.push_back(
*tmpNode);    // Insert PARAM.
                    ++cur_pos;
                }

            }


            
--level;
            k
=0;
        }

        
else
        
{
            pstr[k
++]=str[cur_pos++];
            
continue;
        }


        memset(pstr,L
'',sizeof(pstr));
    }

    
return &list.front();
}

 

 

线性坐标系到对数坐标系的变换——EMC测试曲线的绘制

       
    绘制测试曲线的核心函数为
CEMCView::DrawFrameCurve()函数。该函数可以根据测试的频段和该频段的测试数据在对数坐标系的对应位置绘制测试曲线。DrawFrameCurve()函数绘制曲线的流程图如图1所示:


图1 测试流程图


 

    图2 R3131A屏幕显示的线性坐标系


    每次测试,Advantest R3131A频谱仪都会对所测频段采样501个点,每个点对应的测试值不小于512,如图3-29所示。大于3392的数值不在频谱仪屏幕上显示,但会记录下这个值,因为这个值也是有效的。Advantest R3131A频谱仪默认纵轴每格代表10dB,因此频谱仪的测试值3392-512=2880对应着80dB。对于在计算机上绘制的对数坐标系而言,若纵轴表示0M dB,因此对数坐标系纵坐标最大值对应的频谱仪测试值为:


         Ytop为对数坐标系左上角对应的计算机屏幕上的纵坐标值,Ybot表示对数坐标系左下角对应的计算机屏幕上的纵坐标值,y表示对数坐标系中某个测试点对应的计算机屏幕上的纵坐标值,d表示该测试点对应的频谱仪测试值,对数坐标系纵坐标从0开始,则:


因此,


                                                  (3-1)

对于横坐标,如果已知测试的起始频率为fstart,起始频率对应的计算机横坐标为xstart,测试终止频率为fstop,测试终止频率对应的计算机横坐标为xstop,该频段的起始频率为ffrmstart,结合图3-16可得该频段起始频率对应的计算机坐标有:


因此

 

 

         如果每个测试频段有N个采样点,也即有N个测试数据,那么该频段中第i个数据对应的频率为fi,且

式中,     ffrmstop——测试频段的终止频率。

i个数据对应的计算机横坐标为:

                     (3-2)

         如果在某个测试频段内的第i个数据的测试值为d,则该点对应的计算机屏幕坐标为(xi,yi)xi、yi可分别根据式(3-1)和式(3-2)求出。

 

PS:

    CSDN不支持把Word中的公式自动保存为图片实在是太难用了,每次都要自己先把公式转化为图片然后再插入到文章里,麻烦!

案例检索——EMC-CBR故障诊断研究(三)


案例检索的核心任务就是计算新案例和案例库中的案例的相似度,而相似度要由案例的故障特征和系统结构确定。

1)案例属性之间的距离

由于系统结构直接影响着EMI的大小,即便两个设备具有相同的物理布局但采用不同的元件或材料,其EMC的效果也是不同的,因此两个案例之间的“系统结构”之间的距离不仅和谓词相关,而且要考虑到谓词函数的参数——谓词往往表示结构布局,而参数往往表示材料或元件。因此,在计算案例属性之间的距离时,对于案例属性中的谓词和参数要赋予不同的权值。

case-i的某个属性(故障特征或系统结构)m个谓词,case-j对应的该属性有n个谓词,且二者相同的谓词个数为k,其中min(m,n)k。若该属性中,谓词权重为,参数权重为,则定义两案例在该属性的距离为

其中, case-icase-j中第h个相同的谓词的参数之间的匹配程度,有

        

该定义只考虑了相同的谓词以及相同谓词对应的参数之间的关系,两案例相同的谓词越多,则这两个案例之间的距离越短。

         2)案例的相似度

         d1为两案例“故障特征”属性的距离,d2为两案例“系统结构”属性的距离,由于0<=d1,d2<=1,则可以定义两案例的相似度为:

其中,SIM1SIM2分别为两案例“故障特征”属性、“系统结构”属性对应的相似度,且

i=1,2

         案例库中和目标案例相似度最大的案例,就是最佳匹配案例。当然,有可能出现即便是最佳匹配案例和目标案例的相似度也比较小的情况。可以为了保证最终结果的正确性,设定一个相似度的阈值,低于该阈值的相似度的案例不能作为最终结果使用,必须利用专家系统对结果进行修正。

半旗.默哀.国歌

document.domain="qq.com";
//parent.qZEditor.callback();
function init(){
var key = location.hash.substr(1);
document.editorID = key;
parent.editorHash[key].callback();
}

    今天下午,2008年5月19日14:28分,我来到北京理工大学东操场,和其他近万名师生一起,集体为汶川大地震遇难者默哀。这次活动是学校倡议的,本没有安排我们即将毕业的硕士参加,但我们还是去了。下午2点操场上已经占满了人,BTV对这次活动现场直播。默哀前,低年级的小朋友们的表现令我很不满,他(她)们的态度很不严肃,部分女生的着装也不庄重,表现不合时宜。但是当开始默哀时,他/她们能够迅速进入状态,没有人交谈,没有手机响起,没有骚动,还是难能可贵的。

    默哀过程中,防空警报声、汽车鸣笛声此起彼伏,为逝者而悲号,为生者而祈祷。尽管我还年轻,尽管我远离灾区,但我脑海中再一次涌现出那句话——“人应该赶快生活”。

   
    默哀完毕,根据指挥老师的提议,我们和自己身边的同学手拉手,唱起了久违的国歌。国歌是那么熟悉有那么陌生——熟悉是因为从小就听着国歌长大,陌生是因为很少有机会自己亲口唱起它。今天,总算给了我们一个唱响国歌的机会。
 
    今天凌晨1:30从女朋友家出发走向她们县的火车站,3点多坐火车回北京。在由北京西站回学校的公交车上,看到了(确切地说是听到了,人多,只能听到电视的声音,却看不到图像)电视里天安门降半旗的情景。国旗护卫队迈着整齐的步伐走到旗杆前,奏国歌,升国旗,国旗升好后,只听一位国旗护卫队员高喊“降——半——旗——”,我身体一颤,莫名的辛酸油然而生,之后就是很长时间的寂静。平时公交车里即便寥寥数人也异常热闹,但今天早晨,却非常安静。尽管只有少数人能看到电视屏幕,但大多数人都在倾听,神情都很肃穆。
 
    路过几家宾馆和国营机构,鲜艳的五星红旗都在旗杆的半空中摇晃着,向路人诉说着这个国家正在经历的苦难……

硬盘安装Fedora 9成功

       昨天晚上半夜(5月13日晚23:56分)我登陆到fedora项目的主页一看,My God,Fedora 9已经可以下载了!我立马开始下载。由于速度只有几百K/S,只好先睡觉。
       今天早上6:30就起来了,第一件事就是准备将Fedora 9刻录到光盘,然后准备安装。几天前就已经将光盘买好了,Nero也装好了,就等着刻呢。非常恶心的是,我一连试了三张白盘,一次也没刻录成功!气得我七窍生烟!还不到两年,破本本的刻录光驱就不能刻录了!Shit!
       8点多感到实验室,半路上就决定了要从硬盘安装Fedora 9。上网查资料,N多,感觉还蛮简单的。照做。下载Grub for Linux,设置好,设置menu.lst。问题来了,将Fedora的iso映像放到哪里呢?还有就是我的Windows分区是NTFS格式的,留出来的空闲分区才15G,太小了,怎么改变分区大小呢?
         有人说DVD版的Fedora要解压缩,有人说不用,但都要放到Fat或fat32分区里。上网,下载了Partition Manager,结果安装时说不能用于Windows Server 2003下面。Shit@!只好重新搜到了一个可以用于Server 2003下调整分区的软件。调好之后,重启进入grub安装。结果进不去Fedora安装界面!
         找遍了所有原因,才发现自己在menu.lst中的initrd写成了intrid!唉!改正后终于进入了安装界面。这时却又找不到Fedora映像了。哦,原来我直接将iso给解压缩了。shit!不得不重新将Fedora的iso映像拷到fat32分区,重新装,一切OK!
       
       大二的时候就装过Red Hat Linux9,当时装了玩了几次,感觉很不好用,就给删了。大四毕业又玩了一段时间Fedora core3,感觉好多了。后来在实验室电脑上装了Fedora 7,感觉很好。前几天用Wubi在笔记本上装Ubuntu8.04,结果不成功,很是郁闷。在实验室台式机上却成功了。进到Ubuntu后完了几次,感觉很不爽——界面不怎么好看,桌面版自带的软件少得可怜,要通过网络安装,可我们平时很难上外网(拜教育网所赐)。总的说来,还是比较喜欢Red Hat 的产品,毕竟人家定位在企业级应用。而且听说Linus用的Linux版本就是Fedora哦。
     最近一年来逐渐对Linux情有独钟,也越来越不喜欢用Windows了。毕竟这是两种不同的模式,两种不同的文化。现在对知识产权的意识越来越强烈了,以前一直用盗版,现在不想用了,但又没钱买正版Windows下的软件,因此还是转到Linux的好。
      向开源进军!

PS:硬盘安装Linux的详细设置请参阅

以WinGrub 引导安装Fedora 4.0 为例,详述用WinGrub来引导Linux的安装

EMC测试曲线选择缩放功能的实现

EMC测试曲线选择缩放功能的实现

 

EMC测试完成后,一般以曲线的形式显示在对数坐标系中。由于测试数据非常多,而对数坐标常常使得大量的数据分布不均,经常在一些地方使得测试曲线叠加在一起,看不清楚具体的数值和趋势。一幅CE102测试曲线如图1所示:

1 CE102-全频段测试曲线图

由图可见,要想看清2MHz以后的数据只有两种方法:选择感兴趣的频段重新测试,或者将感兴趣的频段放大。重新测试需要耗费更多的时间,也是一种无谓的重复劳动,而且在某些情况下无法实现(没有频谱仪或接收机在身边的时候)。选择感兴趣的频段放大,既节省时间,又便于查看。

本程序中选择放大的使用方法极为简单,按下工具栏的放大按钮,在测试坐标系中按下并拖动鼠标左键选择频段,释放鼠标左键,这样就可以将选择的频段的测试曲线放大了。



2 选择放大的方法

在图2中绿色虚线就是按下并拖动鼠标左键进行选择时的效果。

放大后的曲线如图3所示:

3 选择放大后的测试曲线

放大后按下工具栏的缩小按钮就可以重新显示原始的测试数据。

 

下面介绍一下程序的编写。

选择频段的过程至少包含三个相关函数:void CEMCView::OnLButtonDown(UINT nFlags, CPoint point)void CEMCView::OnLButtonUp(UINT nFlags, CPoint point)void CEMCView::OnMouseMove(UINT nFlags, CPoint point)。三个函数的定义如下:

void CEMCView::OnLButtonDown(UINT nFlags, CPoint point)
{
    
// TODO: 在此添加消息处理程序代码和/或调用默认值
    m_lBtnDownPoint=point;    // 记录按下鼠标左键的点
    m_bIsLBtnDown=TRUE;

    CScrollView::OnLButtonDown(nFlags, point);
}

void CEMCView::OnMouseMove(UINT nFlags, CPoint point)
{
    
// TODO: 在此添加消息处理程序代码和/或调用默认值
    if(m_bIsWorking && m_bIsLBtnDown && !m_bOldVersion)
    
{
        
if(point.x>m_maxMouseX)
            m_maxMouseX
=point.x;
        
if(point.y>m_maxMouseY)
            m_maxMouseY
=point.y;

        CRect rect(m_lBtnDownPoint.x
-1,m_lBtnDownPoint.y-1,m_maxMouseX+1,m_maxMouseY+1);

        CClientDC dc(
this);
        
// Create a geometric pen.
        LOGBRUSH logBrush;
        logBrush.lbStyle 
= BS_SOLID;
        logBrush.lbColor 
= RGB(17,255,17);//RGB(0,51,51);
        CPen pen(PS_DOT|PS_GEOMETRIC|PS_ENDCAP_FLAT,2&logBrush);
        CPen
* pOldPen=dc.SelectObject(&pen);

        
this->InvalidateRect(&rect);
        
this->UpdateWindow();

        
// 绘制虚线矩形
        dc.MoveTo(m_lBtnDownPoint);
        dc.LineTo(m_lBtnDownPoint.x,point.y);
        dc.MoveTo(m_lBtnDownPoint.x,point.y);
        dc.LineTo(point);
        dc.MoveTo(point);
        dc.LineTo(point.x,m_lBtnDownPoint.y);
        dc.MoveTo(point.x,m_lBtnDownPoint.y);
        dc.LineTo(m_lBtnDownPoint);

        dc.SelectObject(pOldPen);
    }

    CScrollView::OnMouseMove(nFlags, point);
}

void CEMCView::OnLButtonUp(UINT nFlags, CPoint point)
{
    
// TODO: 在此添加消息处理程序代码和/或调用默认值
    m_bIsLBtnDown=FALSE;

    m_zoomStartX
=m_lBtnDownPoint.x;
    m_zoomStopX
=point.x;

    
// 更新视图
    if(!m_bOldVersion)
    
{
        
if(m_bIsZoomed)
        
{
            m_bDrawZoom
=TRUE;
        }

        
this->Invalidate();
        
this->UpdateWindow();
    }


    CScrollView::OnLButtonUp(nFlags, point);
}

由于测试是分成多段进行的,测试曲线的绘制也是分成多段来进行,因此选择放大时也要考虑到所选择区域是否包含关键点(频率段与频率段之间的边界点)。因此选择放大频段的显示也分为三种情况进行考虑:选择的起始点和终止点在同一分段内、在两相邻频段内、在不相邻的不同频段内。

由于选择放大显示的代码较多,只介绍编程思路吧。首先根据鼠标左键选择的起始点和终止点的X坐标确定起始频率和终止频率,然后确定起始点和终止点的相对位置(即上段所说的三种情况),接下来重绘坐标系,针对三种不同情况分段绘制曲线,最后重绘军标线。

【原创】案例推理(CBR)中案例的存储与检索——EMC-CBR故障诊断研究(二)

   最近一直在研究案例推理,看到好多论文都在大谈CBR理论,很是崇拜。不过一直有一个问题没有搞懂,就是案例(case)到底是怎么存储的呢?

       CBR的大师们如Schank、Koldner等人一般用动态存储模型

Dynamic Memory Model,如

Ray Bareiss者一般采用 类别&案例模型(The Category & Exemplar Model),中科院史忠植教授也提出了自己的一种由于以网络发展而来的方法。大师们的境界非我一AI菜鸟所能领悟,大师们的方法也非我能够掌握。幸好看到国内外有几篇论文比较浅显,说可以用关系型数据库来存储,一笔带过,很是潇洒,但我更加一头雾水了。

       迷糊了半个月,在高济教授等编著的《人工智能基础》(高等教育出版社,2002)第三章看到了语义网络表示方法,文中同时给出了如何将语义网络转换为一阶 谓词表示的方法,因此茅塞顿开,有了《EMC-CBR故障诊断研究(一)》一文中的成果。但一阶谓词逻辑又该如何存储到数据库呢?

        在《人工智能基础》一书的第122页作者给出了一个简单的语义网络和该语义网络的一阶谓词表示,书中阐述了一种将谓词作为数据表的列的一种存储方法。但是 思来想去该方法存在一个问题,一旦语义网络比较复杂(显示问题中随便一个问题的语义网络表示都很复杂),数据表中的列将迅速膨胀;更严重的问题是,对于不 同的EMC故障案例而言,用于描述该案例的谓词数目是不确定的,因此数据表的列数也无法确定,这在关系型数据库中是不可能实现的!我再次陷入迷茫。

       God bless us!今天终于想出一种存储方法。思路就是将一阶谓词作为一个对象来进行存储。一阶谓词的属性可以作为数据表中的列,而谓词函数以及谓词的参数作为行来存储。表的设计如下:

CBR

列号

列名

数据类型

能否为NULL

说明

1

predicate_no

smallint

Not Null

谓词编号,主键,标识,种子1,增量1

2

case_no

varchar(15)

Not Null

案例号,一般为case+数字

3

predicate

varchar(30)

Not Null

谓词

4

predicate_property

varchar(30)

Not Null

谓词属性,取值范围fault_character, phisical_structure, causation, settlement

5

param_nums

tinyint

Not Null

谓词参数的个数,取值范围12,默认为2

6

param1

varchar(50)

Null

谓词函数的第一个参数

7

is_param1_digit

bit

Null

param1是否为数字类型,默认0

8

param1_unit

varchar(8)

Null

param1的单位,如果param1为数字类型,则该项一般不为Null

9

param2

varchar(50)

Null

谓词函数的第二个参数

10

is_param2_digit

bit

Null

param2是否为数字类型,默认0

11

param2_unit

varchar(8)

Null

param2的单位,如果param2为数字类型,则该项一般不为Null

12

has_sub_predicate

bit

Null

谓词函数是否以别的谓词作为参数,默认为0

13

sub_predicate_no

smallint

Null

作为参数的谓词的编号

本案例库采用SQL Server2005 Express Edition来实现:

如果要查询案例6EUT的物理结构特征,可以用如下查询语句:

SELECT predicate, param1, param2

FROM Cases

WHERE (predicate_property = 'phisical_structure') AND (case_no = 'case6')

查询结果如下:

【注】:版权归作者所有,转载或引用请注明出处。

关于EMI多线程分段测试方法的说明

2008年5月15日修正版

利用频谱仪进行电磁干扰测试时,考虑到显示效果,点击测试对话框的“开始”按钮之后视图应该立刻切换到对数坐标系而不是等待测试完成后才切换,因此,必须采用多线程测试方式,也即,需要增加一个测试线程用来向频谱仪发送测试指令。该测试线程定义为CTestThread,由MFCCWinThread派生而来。主线程(在本程序中为CChildFrame类)和辅助线程(CTestThread)之间利用消息进行通信,消息处理函数的参数用来传递测试工程设置数据的指针。

另一方面,根据GJB152A-97《军用设备和分系统电磁发射和敏感度测量》规定,在不同的测试频段,接收机的6dB带宽(RBW)必须采用不同的数值,如下表所示:

 

频率扫描测量6dB带宽

频率范围

6dB带宽

30Hz1KHz

10Hz

110KHz

100Hz

10250KHz

1KHz

250KHz30MHz

10KHz

30MHz1GHz

100KHz

>1GHz

1MHz

如果一次测试横跨多个频段,那么就必须分为多段进行测量。另外,由于接收机或频谱仪一般为数字设备,受限于测量仪器的测量采样精度,如果频段较宽,因为一次测量采样点数是固定的(本文所用频谱仪一帧采样501个点),此时往往也需要将整个测试频段划分为几个更小的频段分段测量。本文中将这样的划分后的一个小频段称为一帧。

综上所述,一次测试是一个多线程逐帧测试的过程。辅助线程CTestThread用于向频谱仪发送测试指令,发送完一段测试指令,便休眠等待;主线程CChildFrame接收测试结果(ASCII格式),接收完一帧向辅助线程发送消息进行下一段测试,同时发送消息更新CEMCView中的测试曲线。测试流程如下图所示:

以下为最初的版本,予以保留。

################ 关于多线程测试方法的说明 ################

      利用频谱仪进行电磁干扰测试时,考虑到显示效果(点击“开始测试”按钮之后视图立刻切换到对数坐标系而不是等待测试完成后才切换),必须采用多线程方式,也即,增加一个TestThread用来向频谱仪发送测试指令。
       主线程和辅助线程(CTestThread)之间利用消息通信。根据GJB规定,CE102测试在10KHz~1000MHz之间需要分为三段:10KHz~250KHz(RBW:1KHz)、250KHz~30MHz(RBW:10KHz)、30MHz~1GHz(RBW:100KHz)。因此,一次测试工程势必要分为多段测量。辅助线程用于向频谱仪发送测试指令,发送完一段测试指令,便休眠等待;主线程接收测试结果(ASCII格式),接收完一帧向辅助线程发送消息进行
下一段测试。辅助线程也可以通过::PostMessage函数向主线程发送消息。
      测试流程如下图所示:


   file="/Best4cUserFiles/20080503/17143_1209816609220";showImage();    如需源代码,请联系本人。

PS:今天本人第一次使用CSDN的Best4c画流程图,很是惊异于网络编程的强大,难怪微软深刻感到Google等公司的在线Office的压力,也要推出自己的Live Office呢。(貌似Best4c采用了Adobe的Flex技术?请求达人指教)但是必须指出的是,Best4c在很多方面还不够理想,仍需努力……

利用自定义消息处理函数的WPARAM或LPARAM参数传递指针

有自定义消息:

#define WM_TEST WM_USER+121

消息处理函数:

afx_msg void OnTest(WPARAM wParam,LPARAM lParam);

该消息是一个主线程向辅助线程发送数据的消息。
主线程中发送消息的代码:

       m_param.pDoc=pDoc;
    m_param.pSpecAnlyz
=m_pSpecAnlyz;
    
//    CWinThread* pThread=AfxBeginThread(ThreadTest,static_cast<LPVOID>(&m_param));
    m_pTestThread=(CTestThread*)AfxBeginThread(RUNTIME_CLASS(CTestThread));
    Sleep(
200);
    
// 必须先把&m_param转化为void*指针,然后才可以进一步强制转化为WPARAM
    m_pTestThread->PostThreadMessageW(WM_TEST,(WPARAM)((void*)&m_param),1);// 发送测试消息

 m_param为自定义类型,其成员为指针变量,因此 m_param无法强制转化为WPARAM。但是m_param的地址指针和WPARAM一样,都是4个字节,因此可以将m_param的地址指针起那个只转换为WPARAM。在VC.NET2005中,直接转换编译报错。考虑到void * 之后,先将&m_param转化为void*指针,然后才可以进一步强制转化为WPARAM

同样的,在OnTest中,必须先将wParam强制转换为void * ,然后进一步转换为其他类型。

PS:网上有高手指出,局部指针变量最好不要作为wParam或 lParam传递,因为当消息响应时改变量可能已经不在了。