Install windows DomU for xVM on Nevada 75a

1. Configure the vnc password and default nic for xend service.

    # svccfg -s xvm/xend setprop config/vncpasswd="abc123"
    # svccfg -s xvm/xend setprop config/default-nic="bge0"
    # svcadm refresh xvm/xend
    # svcadm restart xvm/xend

The NIC must support the latest version of GLD (version 3), such as bge, e1000g, xge, nge,
and rge devices. The way to determine if a NIC is GLDv3, run the dladm(1M) command with the 'show-link', and look for links that are not of type 'legacy'. In my case, my NIC device is bge0.

Refer to "Download, Installation, and Configuration Information"

2. Use virt-install(1M) to install the windows domain.

    # export DISPLAY=:0.0
    and you need enable your Xserver access from localhost

    # virt-install -n winxp --hvm -r 512 --vnc -f /export/winxp/winxp-disk.raw -s 10 -c /windows/media.iso
    # vncviewer :0

This will create a fully virtualized guest domain named as "winxp", 512M memory, 10G disk space. And then start to launch the installer in the installation CD image. With virt-install(1M), you DON NOT need qemu-img utility (get it from blastwave) to create the domain disk file.

Refer to "Using virt-install to Install a Domain"

3. After the initial installation is finished, that domain is poweredd off, to start it again

    # xm start winxp
    # vncviewer :0
  
While the post installer still needs load something from the CD image, while seems that the newly created domain does not remember its CDROM configuration. So I created a python configuration file ("xm block-attach" maybe a better solution), following the instructions in "Instant Windows—Recipe for Running Windows as a Guest on Solaris xVM", the only difference is I changed the vif to [ '' ] .

    # xm shutdown winxp
    # xm delete winxp
    # xm new winxp.hvm
    # xm start winxp

4. Finish the post installation of windows, and don't forget to install the security patches and antivirus software :)

Boot solaris xVM off a ZFS root filesystem

Thanks Jürgen Keil for the kindly help, I could boot solaris xVM off a ZFS root filesystem now.

Just one extra step to http://www.opensolaris.org/os/community/zfs/boot/zfsboot-manual,

# mount -F lofs -o nosub / /mnt
# cp /mnt/lib/libc.so.1 /zfsroot/lib

That's because the /lib/libc.so.1 is re-mounted by another more suitable libc for the running processor. E.g., for my Intel dual core, it's

$ mount -p | grep libc
/usr/lib/libc/libc_hwcap3.so.1 - /lib/libc.so.1 lofs - no

While, under xVM, the "sep" cpuid (see isainfo -v) feature isn't available.

法拉利惊天逆转,汉密尔顿痛失冠军

与其说莱科宁奇迹般地赢得了年度冠军,不如说汉密尔顿不可思议地丢掉了唾手可得的冠军。当比赛还有十几圈结束的时候,Rosberg和BMW两位车手的3次缠斗,险些让莱科宁的夺冠泡汤,因为一旦两辆车发生碰撞退赛,则汉密尔顿将以第5名完赛,也就将夺得冠军。直到最后汉密尔顿以第7位跑完全程,才算尘埃落定。真是惊险啊...

昨夜是一个创造奇迹的夜晚,傅家俊赢了,纳尔班迪安也赢了,呵呵...

SunPinyin代码导读(杂类)

TDoubleAnatomy and TLongExpFloat:

TDoubleAnatomy是一个union,用来把一个双精度的浮点数,分解为符号位、指数和尾数。这个方法适用于所有符合IEEE-754浮点数的平台。下面是双精度浮点数的二进制布局:

这个浮点数对应的有理数为:

V = sign * 2^{exp} * (1.fract)

注意:其中指数使用的是移码,对双精度浮点数来说,指数是11位,因此实际的指数要减去2(11-1)-1,即0x3FF。TDoubleAnatomy::clearExp()方法将浮点数的指数部分清除(即设置为0x3FF),然后再调用TDoubleAnatomy::getValue()得到的双精度浮点数,就是sign * (1.fraction),这也就是TLongExpFloat::m_base的值。且在Sunpinyin中,所有的概率值都不会出现负值。

直接使用double进行概率值的连续相乘运算,容易溢出。而TLongExpFloat表示的范围要大了很多,相当于将64位(1+11+52)的浮点数扩展到96位(32+64)。因此能缓解溢出的状况。TLongExpFloat重载了部分算术操作符,让我们可以像对一个primitive数据类型那样进行比较和乘除的操作(但不支持加减)。

你可能会问另外一个问题,为什么不用对数将乘法操作转换为加法呢?这是因为插值的原因,回忆一下插值的公式。如果使用对数,必须将对数形式的结果转换回来,才能执行加法。

处理计算bow时由于浮点数累加所造成的误差

在计算bow时(参见CalcNodeBow()),可能因为浮点数的连续相加造成的误差,而导致sumnext或sum出现大于1的情况,此时使用下面的公式计算bow:

原来要考察的是sumnext和sum离1.0距离的比值,现在因为误差的原因,两者都有可能稍稍超过1.0,因此将比较的基准点向右移动一些(即γ = Max(sumnext, sum)+δ,且δ是一个较小的值,0.0001)。

后面的话

如果再想到或遇到其他一些杂类的题目,我会持续更新这个entry。而SunPinyin代码导读这个blog系列,就此告一段落了。希望能对您了解SunPinyin的架构和代码细节,有所帮助。当然,我对算法的理解以及行文当中,肯定有所偏误,请大家多多指正。

下一步,我还会将这个系列,翻译为英文(头痛啊)。顺便仔细review一遍,并整理成一份文档,发布在input-method项目网站上。

SunPinyin代码导读(十三)

这一回我们来考察History Cache:

class CBigramHistory{
    typedef unsigned                              TWordId;
    typedef std::pair<TWordId, TWordId>           TBigram;
    typedef TWordId                               TUnigram;
    typedef std::map<TBigram, int>                TBigramPool;
    typedef std::map<TUnigram, int>               TUnigramPool;
    typedef std::deque<TWordId>                   TContextMemory;

    TContextMemory          m_memory;
    TUnigramPool            m_unifreq;
    TBigramPool             m_bifreq;
};


从上面的定义可以看出,m_unifreq和m_bifreq分别保存了每个Unigram和Bigram出现在History Cache中的次数。而m_memory是一个双向队列,context_memory_size是其空间的上限,缺省为8K

CBigramHistory::incUniFreq(ug):

将TUnigram ug的出现次数加1。

CBigramHistory::decUniFreq(ug):

如果m_unifreq map中存在这个unigram,且出现的次数大于1,则减1;如果出现次数已经为0,则将其从m_unifreq中删除,以避免m_unifreq这个map占用的空间过大。

CBigramHistory::uniFreq(ug):

如果ug不是stopword(在CBigramHistory::initClass()时初始化,且要和slm词典的id相一致),且存在于m_unifreq中,则返回这个key对应的value。否则返回0。

CBigramHistory::incBiFreq(bg):

将TBigram bg的出现次数加1。

CBigramHistory::decBiFreq(bg):

如果m_bifreq map中存在这个bigram,且出现的次数大于1,则减1;如果出现次数已经为0,则将其从m_bifreq中删除,以避免m_bifreq这个map占用的空间过大。

CBigramHistory::biFreq(bg):

如果bg不是<DCWID, DCWID>,且存在于m_unifreq中,则返回这个key对应的value。否则返回0。

CBigramHistory::memorize(its_wid, ite_wid):

将词的序列[its_wid, ite_wid)记录下来。首先我们要在当前的历史记录中,插入一个分割符(DCWID,相当于</s>),以和前一个流分隔开来;如果此时m_memory已经达到上限,则需将m_memory头部的一个词弹出,并且要减少相应unigram和bigram的出现次数。接下来,遍历[its_wid, ite_wid),假定当前的词为Wi(i从0开始),将Wi加入到m_memory中,将Wi出现的次数加1,并将bigram <Wi-1, Wi>(W-1为DCWID)出现的次数加1;期间,如果m_memory达到了上限,和上面的操作一样,将头部的记录弹出。

当视图类调用doCommit()时,会调用这个方法将用户提交的候选句子记录下来。

CBigramHistory::bufferize(buf_ptr, sz):

将m_memory持久化到buf_ptr指向的缓冲区中,同时注意大端和小端的问题。而m_unifreq和m_bifreq并不保存。

CBigramHistory::loadFromBuffer(buf_ptr, sz):

加载已持久化的History Cache,并统计unigram和bigram的出现次数。

CBigramHistory::seenBefore(wid):

如果wid不是分割符,且非stopword,同时存在于m_unifreq中,则返回true。

CBigramHistory::pr(bigram):

求P(bigram.second|bigram.first)的概率值。以Bigram <x, y>举例来说,uf0是C(x), bf为C(x, y),而uf1是C(y)。求解概率的计算公式为:

P(y|x) = \lambda \frac{C(x,y)}{C(x)+0.5} + (1-\lambda) \frac {C(y)}{actual\_size + unused\_size/10} \\ (\lambda == 0.68)

这个公式是Pml(y|x)、Pml(y)的插值,0.5是为了平滑Pml(y|x),而(actual_size + unused_size/10)是为了防止刚开始的时候history cache的size过小。最后这个概率值再和Pslm(y|h)进行插值,参见ime/src/imi_context.cpp#902

CBigramHistory::pr(its_wid, ite_wid):

y = *(ite_wid-1),x = *(ite_wid-2),求P(y|x)。

CBigramHistory::pr(its_wid, ite_wid, wid):

y = wid, x = *(ite_wid-1),求P(y|x)。

这个blog系列终于要接近尾声了,下一回再介绍一些边角料的东西。

Yet another SLM based Pinyin input method

http://sourceforge.net/projects/novel-pinyin

Epico announced his developing project on linuxsir.org, which based on scim-pinyin, and could be considered as a successor of scim-pinyin. And James was the supervisor when he was in Novell.

From his source code, I guess its language model maybe a bi-gram model, while it would support multiple Pinyin schemes like scim-pinyin does.

Actually, there is another SLM based IM that I noticed long time ago, Davepy, which is also hosted on sourceforge, but not yet opened any source code (the 1.0 release for Windows is available for downloading, and the author promised it would be opensource'd after a stable release).

SunPinyin代码导读(十二)

这一回我们以以“经典风格”为例,介绍对User Editing(包括insertPinyin,erase,move cursor,selection等)的处理。


class CIMIClassicVIew {
    m_CursorBone: 当前光标所在的Bone
    m_CursorIdx:  光标在m_CursorBone中的位置索引
    m_CandiBone:  候选列表起始的Bone
    m_CandiFirst: 当前窗口中第一条候选在整个候选列表中的索引
    m_TailSetence:从m_CandiFirst开始的尾句
};

考察下面的例子,m_CursorBone指向的是'yin'所在的Bone;m_CursorIdx为2,指向的是'n'所在的位置;而m_CandiBone指向的是'pin'所在的Bone;因为没有翻页,所以m_CandiFirst为0;而候选1,即“拼音输入”,就是m_TailSetence。

在经典风格中,向左移动光标,意味着用户可能要修正以前的UserSelection,因此要清除以前的用户选择并重新搜索。而向后移动,后续的可能操作是插入、删除和添加等操作,不影响当下的候选结果,因此不会重新进行搜索。

CIMIClassicView::moveHome(changeMask, searchAgain):

将[firstBone, m_CursorBone]内所有的用户选择都清除,返回开始新搜索的Bone节点。如果存在用户选择,则从firstBone重新搜索。如果不存在用户选择,则返回lastBone。

CIMIClassicView::moveLeft(changeMask, searchAgain):

如果m_CursorIdx(光标在m_CursorBone中的索引)大于0,则减去1,返回lastBone(即不影响当前的候选结果)。如果m_CursorIdx为0,并且m_CursorBone不是firstBone,则将m_CursorBone左移一个单位,如果移出了当前的候选词范围(也就是进入了前一个候选词范围),则调用m_pIC->cancelSelection()取消其所在的UserSelection,然后调用getCandidates()重新得到候选,将m_CursorIdx设置为m_CursorBone中拼音字符串儿的长度(如果m_CursorBone不是一个拼音类型的Bone,则m_CursorIdx为0)。最后,返回新搜索的起始Bone节点。

CIMIClassicView::moveLeftSyllable(changeMask, searchAgain):

过程和上面的类似,只不过移动是以Bone为单位的,且移动之后,m_CursorIdx设置为0。

CIMIClassicView::moveRight(changeMask):

如果m_CursorIdx尚未到达m_CursorBone的尾部,则m_CursorIdx++并返回。如果已到尾部,若m_CursorBone是一个拼音类型的Bone节点,则m_CursorIdx++(相当于一个音节的边界);否则将m_CursorIdx设置为0,且m_CursorBone向右移动一个单位。最后一种可能(m_CursorIdx == m_CursorBone->m_String.size()),是光标位于音节边界上,将m_CursorIdx设置为0,且m_CursorBone向右移动一个单位,并返回。

CIMIClassicView::moveRightSyllable(changeMask):

将m_CursorBone向右移动一个单位,并相应地设置m_CursorIdx。

CIMIClassicView::moveEnd(changeMask):

将m_CursorBone移动到结尾。

CIMIClassicView::makeSelection(idx, mask):

idx是在当前候选窗口中的索引,和m_CandiFirst相加,得到在整个候选列表中的索引。如果idx<0,则提交preedit并重置IC,然后返回。如果idx在候选列表的范围内,则得到索引下的候选对象cand,调用m_pIC->makeSelection(cand)设置用户选择。将m_CandiBone指向cand的结束位置(还记得么,[m_BoneStart, m_BoneEnd)是候选词对应的区间),如果它是不是一个拼音节点且未到达尾节点,则将m_CandiBone向右移动。如果一直移动到了尾节点,则提交preedit并重置IC;否则,如果m_Cursor位于[cand.m_BoneStart, m_CandiBone)之间,则将m_Cursor指向m_CandiBone,且将m_CursorIdx设置为0。然后将m_CandiFirst设置为0,调用getCandidates()来得到新的候选。

CIMIClassicView::erase(bLeft, changeMask):

bLeft标识是向左还是向右进行删除。如果向左删除,则调用moveLeft()将光标向左移动一个单位。然后,如果m_CursorIdx指向的是音节边界的位置(即m_CursorIdx == m_CursorBone->m_String.size()),且当前光标是用户确定的边界,则调用m_pIC->modifyAndReseg()对该部分重新节点进行切分和搜索,如果这影响到候选列表,则调用getCandidates()重新获取;如果当前光标不是用户确定的边界,且要向右删除,则调用moveRight()将光标向右移动一个单位。如果m_CursorIdx并非指向音阶边界的位置,则修改光标内的拼音字符串,并调用m_pIC->modifyAndReseg()对该部分节点进行重新切分和搜索,如果这影响到候选列表,则调用getCandidates()重新获取。

CIMIClassicView::insertPinyin(keyvalue, changeMask):

如果m_CursorBone是最后一个Bone(也就是在尾部添加拼音字母的情况),则将keyvalue作为一个自动切分的拼音节点,加到一个新的列表skel中,并将m_CursorBone设置为skel.begin(),且m_CursorIdx为1(m_CursorIdx指向的是音阶边界,因为Bone中只有一个拼音字母)。如果m_CursorBone不是一个拼音节点,当m_CursorIdx>0时(通常不会发生这种情况),将[0..cursorIdx)、keyvalue、 [cursorIdx..end)这三个字符串构造为Bone,加入到列表skel中,cursor2自加,m_CursorBone指向中间的Bone,m_CursorIdx设置为1;当m_CursorIdx为0时,将keyvalue作为一个自动切分的拼音节点,加到一个列表skel中,并将m_CursorBone设置为skel.begin(),且m_CursorIdx为0。最后是插入拼音字母的情况,将m_CursorBone压入到skel中,且将keyvalue附加到当前Bone的拼音字符串的尾部,m_CursorBone为skel.begin(),m_CursorIdx自加,且cursor2自加。然后调用m_pIC->modifyAndReseg()对该部分Bone节点重新切分并搜索,如果这影响到候选列表,则调用getCandidates()重新获取。

还有其它的一些insert方法,例如insertNormalChar()、insertBoundary(),都会涉及到对Bone列表的修改,而重新进行切分搜索,并获取更新的候选列表。这里就不多作介绍了。

下一回,我们将介绍“History Cache”相关的内容。

SunPinyin代码导读(十一)

下面的数据结构和方法用来组织候选词列表(即如何利用搜索结果):

class CCandidate

[m_BoneStart,
m_BoneEnd)是这个候选词对应的bone区间;m_WordID是候选词对应的id;m_String是宽字节字符串的指针。通常
m_String就是m_WordID对应的宽字节字符串表示,但有时候选词并不在词表中(例如无效的拼音串儿)。

CIMIContext::getCandidates (bone, result):


果bone是尾节点,则直接返回。如果不是一个有效的pinyin
Bone节点,将bone上的拼音string作为候选,放到result中,并返回。如果这个bone上有用户选择或最佳词,设置cp(候选词和评分
pair),将它保存在以word_id为索引的map中。

然后遍历[bone+1, T1]。对每个Bone节点,遍历其lexicon状态节点,找到起始节点为bone的状态节点(itlex-> m_BoneStart == bone),将pytrie上的词(如果不是拼音节点,则是Bone上的拼音字符串)加入到map中;遍历其lattice状态节点,如果该节点是从参数bone上转移而来的(its->m_pBackTraceNode->m_BoneAfter == bone),则将m_BackTraceWordId对应的候选词,加入到map中。最后,按评分(TCandiRank),对这个map进行堆排序,将排序后的结果保存在result中。

评分的原则是:首先列出用户选择的词,或者位于最佳路径上的词;优先列出从Lattice状态中得到的词;然后是Lexicon中的词。且词的长度越长,评分越高。

CIMIContext::getBestSentence(result, boneStart, boneEnd, original_format):

如果boneStart上没有最佳词,则向前(左)查找,直到某个存在最佳词的Bone,保存在realStart中。沿着最佳路径的这一段([realStart, boneEnd)),将最佳词添加到result中,记录期间有效拼音节点的个数,并返回。

下面的方法,和用户选择的设定和取消有关:

CIMIContext::cancelSelection(bone, update):


意,m_Skeleton上的最佳词(无论是用户选择还是计算确定)是前后相连的。从bone开始向左查找。如果某个Bone(记为uBone)的类型为
UserSelectedBestWord,则将其改为NoBestWordStartHere,设置found为true,退出循环;否则,如果该
Bone是计算确定的最佳词,则退出循环;否则,则继续向左查找,直到第一个Bone。如果found为true,且update参数为true,
uBone开始搜索,并返回它;否则,因为无需搜索,直接返回参数bone。将[firstBone,
bone]这个区间离bone最近的用户选择取消。

CIMIContext::cancelSelectionCover(bone, update):

这个方法和上面的类似,只是当参数bone的类型是最佳词时,直接返回。相当于将[firstBone, bone)这个区间离bone最近的用户选择取消。

CIMIContext::makeSelection(candi):

找到候选candi的起始Bone(m_BoneStart),调用cancelSelection()将其所处的用户选择取消(返回值为boneLeft),并将候选起始Bone的类型设置为UserSelectedBestWord,然后从boneLeft重新搜索。

我们下一回将以“经典风格”为例,介绍对User Editing(包括insertPinyin,erase,move cursor,selection等)的处理。