|
马上加入,结交更多好友,共享更多资料,让你轻松玩转电力研学社区!
您需要 登录 才可以下载或查看,没有账号?立即加入
×
s函数是system Function的简称,用它来写自己的simulink模块。(够简单吧,^_^,详细的概念介绍大伙看帮助吧)可以用MATLAB、C、C++、Fortran、Ada等语言来写,这儿我只介绍怎样用matlab语言来写吧(主要是它比较简单)。 先讲讲为什么要用s函数,我觉得用s函数可以利用matlab的丰富资源,而不仅仅局限于simulink提供的模块,而用c或c++等语言写的s函数还可以实现对硬件端口的操作,还可以操作windowsAPI等。 先介绍一下simulink的仿真过程(以便理解s函数),simulink的仿真有两个阶段:一个为初始化,这个阶段主要是设置一些参数,像系统的输入输出个数、状态初值、采样时间等;第二个阶段就是运行阶段,这个阶段里要进行计算输出、更新离散状态、计算连续状态等等,这个阶段需要反复运行,直至结束。 在matlab的workspace里打edit sfuntmpl(这是matlab自己提供的s函数模板),我们看它来具体分析s函数的结构。它的第一行是这样的: function [sys,x0,str,ts]=sfuntmpl(t,x,u,flag) 先讲输入与输出变量的含义:t是采样时间,x是状态变量,u是输入(是做成simulink模块的输入),flag是仿真过程中的状态标志(以它来判断当前是初始化还是运行等);sys输出根据flag的不同而不同(下面将结合flag来讲sys的含义),x0是状态变量的初始值,str是保留参数(mathworks公司还没想好该怎么用它,嘻嘻,一般在初始化中将它置空就可以了,str=[]),ts是一个1×2的向量,ts(1)是采样周期,ts(2)是偏移量。 下面结合sfuntmpl.m中的代码来讲具体的结构: switch flag,%判断flag,看当前处于哪个状态+ L* R; |3 J, _, m& R" X
/ E+ P3 i0 `" H6 Y# Tcase 0, [sys,x0,str,ts]=mdlInitializeSizes; flag=0表示处于初始化状态,此时用函数mdlInitializeSizes进行初始化,此函数在 sfuntmpl.m的149行
4 l4 L' s1 Y0 ~8 D6 `; }/ }0 v
0 o- v+ w2 Q1 i# F我们找到他,在初始化状态下,sys是一个结构体,用它来设置模块的一些参数,各个参数详细说明如下; B2 l0 M1 L8 l
$ O, [4 J/ p3 _! r& \
size=simsizes;%用于设置模块参数的结构体用simsizes来生成5 Y8 e8 k$ l+ ]+ T1 M; D
6 h, ~8 F8 o S1 D* _% E' B6 o8 Fsizes.NumContStates=0;%模块连续状态变量的个数: K/ u2 Y9 p) t+ S: ], y( l
: M1 i& G, H* Q* T8 o' o \3 Qsizes.NumDiscStates=0;%模块离散状态变量的个数 X5 V5 p, {$ q. |3 |" \
- N/ [! U- e& c6 Y% ?8 e, dsizes.NumOutputs=0;%模块输出变量的个数; v8 v- z# e9 j% u7 f
" g' V. w4 f! c8 Psizes.NumInputs=0;%模块输入变量的个数0 s9 N4 Z, Z4 C: v" Y0 V
! z5 V* I: c: F5 X8 Q
sizes.DirFeedthrough=1;%模块是否存在直接贯通(直接贯通我的理解是输入能直接控制输出)
+ ]: @& I0 G- K4 |/ b
% o6 C1 l3 u) m6 |sizes.NumSampleTimes=1;%模块的采样时间个数,至少是一个$ h9 F& ?) o) ^6 ~3 Y
' d7 L' T" \& T6 O, I
sys = simsizes(sizes); %设置完后赋给sys输出
7 x8 h, w5 E; o' p5 j, J( G
& u- t) O+ C; v+ O8 ?1 @/ P举个例子,考虑如下模型:
: `9 {+ }5 p$ }. o# O( @4 M- U7 r" I& B. _0 x ^
dx/dt=fc(t,x,u) 也可以用连续状态方程描述:dx/dt=A*x+B*u
- ]" y' U& s3 {
" C7 K& K( p/ ?2 n5 C7 G0 j5 \: r; u x(k+1)=fd(t,x,u)也可以用离散状态方程描述:x(k+1)=H*x(k)+G*u(k)
5 P: A! S k* W. q. N1 s" K5 c2 ? W( q2 L- d9 |
y=fo(t,x,u)也可以用输出状态方程描述:y=C*x+D*u
" ~- n* p. d3 e4 Y- M D) o& q2 N3 M7 w9 M+ n
设上述模型连续状态变量、离散状态变量、输入变量、输出变量均为1个,我们就只需改上面那一段代码为:
! |$ j" I9 A7 \8 Y D$ |
, }* ], l2 g* Z- a' _(一般连续状态与离散状态不会一块用,我这儿是为了方便说明)$ ~1 {, S/ e: X
, k! q% A$ P7 H- s8 T S+ G/ ]
sizes.NumContStates=1; sizes.NumDiscStates=1; sizes.NumOutputs=1; sizes.NumInputs=1; ts=1; 其他的可以不变。继续在mdlInitializeSizes函数中往下看: x0 = []; %状态变量设置为空,表示没有状态变量,以我们上面的假设,可改 %为x0=[0,0](离散和连续的状态变量我们都设它初值为0)' T5 {! p! x. z
7 A$ D5 v) T0 h- Z) ]
str = []; %这个就不用说了,保留参数嘛,置[]就可以了,反正没什么用,可能7.0会给它一些意义
, G3 ^* u1 _( \7 a. f" F8 T7 Y9 j2 d! a+ ]% E# H }2 Q
ts = [0 0]; %采样周期设为0表示是连续系统,如果是离散系统在下面的mdlGet%TimeOfNextVarHit函数中具体介绍
9 ^1 W# m0 h8 R6 l# ^5 ~8 {: y7 F) M: v$ \- p# L! r8 Y& z
嘻嘻,总算讲完了初始化,后面的应该快了
0 F& } z" T" o# v8 F) o
: f+ w4 X. W% m+ O- {" ^6 H在sfuntmpl的106行继续往下看:( H: s; P0 I% I, y1 O a3 d
' ~* _8 @+ r. _6 t
case 1,
# \5 h2 ?) H/ S/ q& O% @) q4 S% V% c) V! _. Q# [; w7 z! a
sys=mdlDerivatives(t,x,u);! |! K2 y" n. M( L' T4 A
0 Z; A, l" B" ~flag=1表示此时要计算连续状态的微分,即上面提到的dx/dt=fc(t,x,u)中的dx/dt,找到mdlDerivatives函数(在193行)如果设置连续状态变量个数为0,此处只需sys=[]; 就可以了(如sfuntmpl中一样),按我们上述讨论的那个模型,此处改成 sys=fc(t,x(1),u)或sys=A*x(1)+B*u %我们这儿x(1)是连续状态变量,而x(2)是离散的,这儿只用到连续的,此时的输出sys就是微分8 M0 V: R6 J9 X' G6 X
& e9 t! A$ a4 W& ?2 x4 T
继续,在sfuntmpl的112行:
6 M: N+ m( @& y+ V
0 K) ^9 Z. D6 q3 t ccase 2,
/ P6 X! ^1 r3 z% |5 @7 h8 G3 o' V
sys=mdlUpdate(t,x,u);
6 i# C+ E0 i: z9 Q( v* }1 |# o$ A% v5 K6 Q2 z% A. A: C
flag=2表示此时要计算下一个离散状态,即上面提到的x(k+1)=fd(t,x,u),找到mdlUpdate函数(在206行)它这儿sys=[];表示没有离散状态,我们这而可以改成sys=fd(t,x(2),u)或sys=H*x(2)+G*u;%sys即为x(k+1)看来后面几个一两句话就可了,呵呵,在sfuntmpl的118行0 q! x3 I, S0 b* i4 ~; N( Y
A9 M1 Z' Q) j) i% `- [
case 3,% x- x7 J; m6 `, j( ]' S+ Y( z0 w
7 N' H* K& O& y" j8 a9 r
sys=mdlOutputs(t,x,u);
5 T" f# t; e. B8 U& t4 l
8 K! Z7 V, F( R5 K* _flag=3表示此时要计算输出,即y=fo(t,x,u),找到mdlOutputs函数(在218行),如上,如果sys=[]表示没有输出,我们改成sys=fo(t,x,u)或sys=C*x+D*u %sys此时为输出y
# H/ W/ g' |( I8 m* {/ l" C( I n; V* `
好像快完了,嘻嘻,在sfuntmpl的124行, h0 D9 \( w# V7 v, K
: [4 m7 Q: y# i
case 4, {; ^4 B0 q7 |1 U c" m
, V: H! I/ ?5 }& o" ?2 ^# bsys=mdlGetTimeOfNextVarHit(t,x,u);
! a' b& v' |" Y$ s+ R& i* [/ ~% s' ^- j
, J; z( h( ~9 Z& f0 d9 cflag=4表示此时要计算下一次采样的时间,只在离散采样系统中有用(即上文的mdlInit ializeSizes中提到的ts设置ts(1)不为0)% a% b* h1 y! ^5 U' h
$ U L0 x" H/ [9 {连续系统中只需在mdlGetTimeOfNextVarHit函数中写上sys=[];这个函数主要用于变步长的设置,具体实现大家可以用editvsfunc看vsfunc.m这个例子
! ~+ [6 `% H" C" G. u o) [5 F$ ~2 h# V
+ i0 q6 e! R' B0 w" B" X i最后一个,在sfuntmpl的130行* z& L; s: h5 e# u" Q' M
, ~- [2 m5 v: S3 s X9 [5 E u+ acase 9,
6 M: O3 N- i+ B; j. `% @
, o2 A; z- T; d- Q& E- Wsys=mdlTerminate(t,x,u);
0 l: F7 P4 z$ k6 P6 h# D
7 @* c% {: v* Y' Yflag=9表示此时系统要结束,一般来说写上在mdlTerminate函数中写上sys=[]就可,如果你在结束时还要设置什么,就在此函数中写2 @1 r1 E7 d) b
! i& k; P5 e) F% \; ^# k关于sfuntmpl这个s函数的模板讲完了。 s函数还可以带用户参数,下面给个例子,和simulink下的gain模块功能一样,大伙自己看吧,我睡觉去了,累了
% y, B; u' `, m9 N( e( f5 m/ ^- p+ z# h, v5 h( v8 q3 Z
function [sys,x0,str,ts] = sfungain(t,x,u,flag,gain)1 E4 q3 I9 ^7 P: a& R# `- C. o
9 Z" t1 w3 ~' g" _3 g& p0 Fswitch flag,* k6 E8 e( g t7 b& g, ]4 r& b3 W/ o
5 x0 h! T9 N2 H
case 0,! g1 l* S" x4 `6 a7 o- q
) h9 T" J4 U3 X k; V sizes = simsizes;6 R/ F; x( [9 v0 {: k! `
6 M" Q6 V, o" v3 W( K0 Y9 E
sizes.NumContStates = 0;
" R: M0 t# f0 J( B1 r, {8 {
6 Q0 f! g" `/ @ sizes.NumDiscStates = 0;4 k6 f: _7 X7 B0 s& h- i
1 l; T C+ g& Z( u% q
sizes.NumOutputs = 1;
6 S9 l* v) Q3 }& m X5 f
9 @/ _3 {5 X; H3 [; G- U T A sizes.NumInputs = 1;2 O5 T, R. g8 h5 E* `
9 i, ]- z5 _' a
sizes.DirFeedthrough = 1;) m" }" y& \3 N# N$ r& S# K1 o3 Y
% W8 M0 F) V/ b- t" h
sizes.NumSampleTimes = 1;: x* f7 i! N J4 T- F e: X/ _+ G7 f
/ U ]2 e6 L# o; m% U sys = simsizes(sizes);
1 e* F1 M2 Q+ g5 L; ~* h
! m7 p9 i1 C3 f0 C- L5 c x0=[];
1 f! E& k0 ~8 e3 u& K# s' S% `/ H- f0 ]* ?! s
str=[];
" b. a$ x3 n5 ?! D" X+ ]7 j, ~2 ]3 t/ b
ts=[0,0];' @% ]$ T. i$ o6 h6 ]% Q
1 G1 I* a6 u* k- v: T( J+ |* ecase 3,
( q9 w, J$ M1 a5 A; g4 q+ e
* f8 M+ N$ R. H# V3 j# }. o; Z. ^. c sys=gain*u;- s i# N% e6 T
1 F9 n) L* w m5 g" K6 H/ }' z
case {1,2,4,9}, T! `& v m3 _( w' n- D, X
2 v' H; ~# F! `, Bsys = [];
: k5 `5 s# |. x: S
: F, i/ s) |& bend
0 W7 }, m5 J" e" Z0 ?; P% _7 |, u1 J% } u& p% b( w
做好了s函数后,simulink--user-definedfunction下拖一个S-Function到你的模型,就可以用了。在simulink----user-definedfunction还有个s-FunctionBuilder,他可以生成用c语2 R g. g* j" d4 ] R+ U
言写的s函数$ E) Z1 o: ]" b2 u2 C. M
在matlab的workspace下打sfundemos,可以看到很多演示s函数的程序。 SIMULINK s-function的设计2 |* r5 S. W3 ^$ f& w- R
- h/ @0 ] C& T6 F2 I4 v& rSimulink为用户提供了许多内置的基本库模块,通过这些模块进行连接而构成系统的模型。对于那些经常使用的模块进行组合并封装可以构建出重复使用的新模块,但它依然是基于Simulink原来提供的内置模块。
6 y9 @! p, N* u; x# A/ Q3 t* N+ t: C1 P- m7 u- T5 c6 _
而Simulink s-function是一种强大的对模块库进行扩展的新工具。0 N, K8 n- A; A* Q$ v
3 a+ q, s. ]7 p; I# [+ d j
(一)、s-function的概念
& ^2 v1 ?" F3 H) r( I; c
! `# z( ? S" M; B& ^9 xs-function是一个动态系统的计算机语言描述,在MATLAB里,用户可以选择用m文件编写,也可以用c或mex文件编写,在这里只给大家介绍如何用m文件编写s-function。
, Y# Y6 h7 _' |1 w
' D+ I' R; q& m! N3 M' k6 ~S-function提供了扩展Simulink模块库的有力工具,它采用一种特定的调用语法,使函数和Simulink解法器进行交互。
1 G4 Y' j0 I( J5 [! J- ~2 ~) {
; i5 C9 M) Y" k1 }, B3 [S-function最广泛的用途是定制用户自己的Simulink模块。它的形式十分通用,能够支持连续系统、离散系统和混合系统
; b* l0 T: U0 p5 U, s T
7 j1 T7 w% t' B, F/ p(二)、建立m文件s-function
8 N/ @/ C1 t) R0 [/ P, [
. T) s+ I2 ]' t# g y 1、使用模板文件:sfuntmp1.m 格式: [sys,x0]=function(t,x,u,flag)5 h$ {, [( ^9 N+ Z; l
0 O( g3 Q. P, q2 N4 e0 v$ N: f
该模板文件位于MATLAB根目录下toolbox/simulink/blocks目录下。
/ u$ p( c5 Q0 N0 i) f; e' `) C% |" K3 v" O+ U
模板文件里s-function的结构十分简单,它只为不同的flag的值指定要相应调用的m文件子函数。比如当flag=3时,即模块处于计算输出这个仿真阶段时,相应调用的子函数为sys=mdloutputs(t,x,u)。
$ s4 G, s& L, F }* @# ?
6 R/ Y$ p- L; K' H' H! G模板文件使用switch语句来完成这种指定,当然这种结构并不唯一,用户也可以使用if语句来完成同样的功能。而且在实际运用时,可以根据实际需要来去掉某些值,因为并不是每个模块都需要经过所有的子函数调用。9 ?! K7 E- x* Y9 r& i
# H! [8 m# _! u) L模板文件只是Simulink为方便用户而提供的一种参考格式,并不是编写s-function的语法要求,用户完全可以改变子函数的名称,或者直接把代码写在主函数里,但使用模板文件的好处是,比较方便,而且条理清晰。0 X9 x1 `2 W9 b0 @- W/ p1 r l
使用模板编写s-function,用户只需把s-函数名换成期望的函数名称,如果需要额外的输入参量,还需在输入参数列表的后面增加这些参数,因为前面的4个参数是simulink调用s-function时自动传入的。对于输出参数,最好不做修改。接下去的工作就是根据所编s-function要完成的任务,用相应的代码去替代模板里各个子函数的代码即可。
) u9 H4 O; X0 c1 h9 }& _. a; ^
7 G& F6 ^" Y2 S/ VSimulink在每个仿真阶段都会对s-function进行调用,在调用时,Simulink会根据所处的仿真阶段为flag传入不同的值,而且还会为sys这个返回参数指定不同的角色,也就是说尽管是相同的sys变量,但在不同的仿真阶段其意义却不相同,这种变化由simulink自动完成。
" x& G$ ~$ ~2 c
9 D$ d' F2 B, _/ m& r) hm文件s-function可用的子函数说明如下:
8 \2 y9 O. `6 p0 R0 H! X& f4 W. C; h7 O+ C! t2 i) J
mdlInitializeSizes(flag=0):定义s-function模块的基本特性,包括采样时间、连续或者离散状态的初始条件和sizes数组。
9 U6 t1 N. W' a3 W3 Y) v; U
9 H* M( M# y i, q3 ]; v8 dmdlDerivatives(flag=1):计算连续状态变量的微分方程。
4 F3 t( D# i7 J2 ?5 c
8 M0 m* C) f; c1 @mdlUpdate(flag=2):更新离散状态、采样时间和主时间步的要求。3 I& Z' z* G1 n: }. O
& n6 r1 R3 ^, l6 c9 m' m" O
mdlOutputs(flag=3):计算s-function的输出。
1 I0 ~: @) G6 v" h- `! c
* S* v1 Z9 v* R8 }7 G5 i) j4 J5 GmdlGetTimeOfNextVarHit(flag=4):计算下一个采样点的绝对时间,这个方法仅仅是在用户在mdlInitializeSizes 里说明了一个可变的离散采样时间。6 i& n* E. X- b$ K3 J* Y$ g
; y' c, }$ v2 [5 _; H概括说来,建立s-function可以分成两个分离的任务:. B& {/ l2 J% K, b4 t1 D
: r& w" |7 ~% j$ {* V+ I
初始化模块特性包括输入输出信号的宽度,离散连续状态的初始条件和采样时间。
3 H8 @* ^2 H* G6 J! x2 m, @
, l1 a- P5 s( n- P5 ]3 _& x6 J% z/ `将算法放到合适的s-function子函数中去。
/ i5 `* i' m& h3 p- u1 k+ O1 T
- G- I+ o% r1 Q' D0 P/ C1 S 2 、定义s-function的初始信息$ [% z0 |& L* d' e" I5 v& x
1 w6 h/ E. w0 w; u M' F为了让Simulink识别出一个m文件s-function,用户必须在s-函数里提供有关s-函数的说明信息,包括采样时间、连续或者离散状态个数等初始条件。这一部分主要是在mdlInitializeSizes子函数里完成。
3 t) _3 H) Z- O+ O) K- c: B9 K: tSizes数组是s-function函数信息的载体,它内部的字段意义为:8 a% R, Z1 V8 s, G' `
) w, g- z# |% Y3 g- [
NumContStates(sys(1)):连续状态的个数(状态向量连续部分的宽度)
' M1 h! P" i3 o2 r' _& Z
* ^( J+ @! ~: R2 ~1 X/ ?/ e5 y, iNumDiscStates(sys(2)):离散状态的个数(状态向量离散部分的宽度): y2 b t+ o3 x: B7 G+ B
+ x/ O7 d$ m. y2 o! q. E! Y1 n
NumOutputs(sys(3)):: B5 } I$ J( P
输出变量的个数(输出向量的宽度)
. \& }) g& k' e& f5 T4 \" E( }, I$ b6 A L
NumInputs(sys(4)):输入变量的个数(输入向量的宽度)1 E# M! `+ a$ F9 s+ x0 `+ |
1 S4 s. u' M% K% IDirFeedthrough(sys(5)):有不连续根的数量4 y4 g) L: j& t t' {; j3 M! w
: @ R. b3 _. G" V0 D
NumSampleTimes(sys(6)):采样时间的个数,有无代数循环标志6 @+ [, C) } k/ Q* W6 K% X
0 U6 ` S6 S: `# a) g如果字段代表的向量宽度为动态可变,则可以将它们赋值为-1。
- p" M; o0 k6 ?( U4 K% o5 X% E2 }: y4 v/ ?+ [6 }; a9 h
注意DirFeedthrough是一个布尔变量,它的取值只有0和1两种,0表示没有直接馈入,此时用户在编写mdlOutputs子函数时就要确保子函数的代码里不出现输入变量u;1表示有直接馈入。
# l. U; S8 b' X! {; Z& w. l+ | M1 h# @- w P+ |* k+ K
NumSampleTimes表示采样时间的个数,也就是ts变量的行数,与用户对ts的定义有关。
0 B- \, M1 w5 Z" W' O6 k
' J+ @) p' I) w; y3 y2 Z4 I需要指出的是,由于s-function会忽略端口,所以当有多个输入变量或多个输出变量时,必须用mux模块或demux模块将多个单一输入合成一个复合输入向量或将一个复合输出向量分解为多个单一输出。
! t9 a4 W I+ s& S0 d
/ A' B' i$ g) M6 G3 |! Q# k) q 3、输入和输出参量说明
1 V7 W3 S( b) \) v* b& D. H5 [0 G: I
S-function默认的4个输入参数为t、x、u和flag,它们的次序不能变动,代表的意义分别为:
, J( i) L3 H0 j% r; E9 h6 h. Pt:代表当前的仿真时间,这个输入参数通常用于决定下一个采样时刻,或者在多采样速率系统中,用来区分不同的采样时刻点,并据此进行不同的处理。
. j/ R! k/ }4 w- j: t, }0 D
4 d# v* V1 @ b% w% @x:表示状态向量,这个参数是必须的,甚至在系统中不存在状态时也是如此。它具有很灵活的运用。
7 r( T9 D4 @/ E: N3 L+ ^, m4 @5 M% P% P+ k4 I1 c
u:表示输入向量。
: P$ {5 _6 @+ D
# v! v1 E8 D! eflag:是一个控制在每一个仿真阶段调用哪一个子函数的参数,由Simulink在调用时自动取值。) I) |' x7 i: p g
1 c6 i8 ^! r4 W0 ?" |; E* mS-function默认的4个返回参数为sys、x0、它们的次序不能变动,代表的意义分别为:; X! Z# {/ E, z8 b) M! W
& w% {0 \/ z* V; c+ i3 p# Zsys:是一个通用的返回参数,它所返回值的意义取决于flag的值。
! ]2 ?# j, I/ Y8 g4 e e7 w9 a
7 P! r3 i% u* @5 Ix0:是初始的状态值(没有状态时是一个空矩阵[]),这个返回参数只在flag值为0时才有效,其他时候都会被忽略。
% l0 V0 O9 h1 K. S7 `; j
7 X7 H& R5 ~. m7 S0 s一、有一系统如下:4 d. T3 ?! I% v9 x) Z
8 B h. M Y* M- `, edx1=x2
, q. F& Z, X# s, L& b. W5 ]1 ^4 Z/ H
dx2=9.81*sin(x(1))-2*x(2)+u% w4 i3 |! P& O4 n7 u
7 X# u( x9 M; ^ n$ D
' Z3 C, S& A# K" z求出系统在单位阶跃输入下的x1的状态变化曲线,假设x1,x2初值为0。
2 v, n4 [; D' S; _' I7 d2 p) y# p
function [sys,x0]=dong(t,x,u,flag)
& E9 S- J) B6 h' c, J
5 l1 r5 `3 y- l4 o6 D2 l. x: C* l" Tif flag==0
7 M) A0 Z5 a; h4 h2 W# B b# B/ E; z/ t
sys=[2;0;2;1;0;0];
, y2 |0 I8 \% |* [ W) t5 I" [" m& Z+ }; t _
x0=[0;0];
3 y& `9 h1 i" r9 o' j7 H9 ]9 N; _+ N" q7 c' \- U
elseif flag==13 r$ Z1 P9 W/ } F, G4 D& S6 r
: B/ R0 |/ F9 _$ u3 Csys=[x(2);9.81*sin(x(1))-2*x(2)+u];
. ^' }6 _5 A/ e0 ^; h @9 N+ b$ [$ T+ q( K( m. a* `
elseif flag==3
6 B ]4 _& l) ~' ?+ Q# G6 e( `6 m3 _5 F: O, u6 p! T
sys=[x(1);x(2)];
% A' d) |, E4 G8 X3 N2 z) k4 k# h7 B. S5 f6 X; Y
else
$ E1 t0 E! Z+ {6 i6 d( {0 K: e
6 w0 ~/ Q& q$ F( X. X+ qsys=[];
! c& L4 e M" i/ I! j- t5 K# \. a
) ?$ k. v( x7 X8 O! kend |