1 /* ======================================================================= */
3 /* ----------------------------------------------------------------------- */
4 /* COntrol engineering with MAxima (c) Wilhelm Haager 2009-2019 */
5 /* ----------------------------------------------------------------------- */
7 /* This program is free software; you can redistribute it and/or modify */
8 /* it under the terms of the GNU General Public License as published by */
9 /* the Free Software Foundation. */
12 /* - function bode_plot */
13 /* - option-processing for plot functions renewed */
14 /* - 2012-01-25: bugfix in "phase" */
15 /* - 2012-02-27: "poles" and "zeros" enhanced */
16 /* - 2012-11-02: bugfix in "step_response": variable "t" local () */
17 /* - 2012-12-21: "ratsimp" replaced by "xthru" in many places */
18 /* - 2013-01-05: "nilt" improved */
19 /* - 2013-03-30: "pulse_response" added */
20 /* - 2013-11-16: Rootsepsilon" set to 1.0e-7 (bug in Maxima 5.31) */
21 /* - 2014-01-23: some bugfixes (nilt,...) */
22 /* - 2014-07-14: margin adjustment in bode_plot */
23 /* - 2014-08-10: "xthru" replaced by "ratsimp" in "impedance_chain" */
24 /* - 2014-09-07: "xthru" replaced by "ratsimp" in "transfer_function" */
25 /* - 2014-11-07: Grid lines for Bode-plots */
26 /* - 2014-11-14: Nested lists for graphic options enabled */
27 /* - 2015-01-09: "//" - Parallel-Operator for electric resistances */
28 /* - 2015-04-26: bug in "hurwitz" removed */
29 /* - 2015-06-21: function "product_form" added */
30 /* - 2015-06-22: function "asymptotic" added, "phase" rewritten */
31 /* - 2015-10-11: sum_form instead of standard_form; minor changes */
32 /* - 2015-10-24 bugfixes, minor changes (product_form, plot) */
33 /* - 2015-11-11 adaptations for wxMaxima 15.08 (wxplot_size) */
34 /* - 2016-01-10 /_ infix operator and //_ postfix operator declared */
35 /* - 2016-06-05 bug in _COMA_ppo removed (thanks to Daniel Volinski) */
36 /* - 2016-07-25 sum_form enhanced; new function npartfrac */
37 /* - 2016-08-05 sallrrots, spartfrac, silt added */
38 /* - 2016-09-19 bug in product_form removed (thanks to Klaus Weichinger) */
39 /* - 2017-02-20 new gain_crossover, new phase_crossover etc. */
40 /* - 2017-05-07 bode_plot: option "logy=dB" added (P.Heidrich, S.Milani) */
41 /* - 2017-06-08 sum_form extended (second parameter: 1...6 ) */
42 /* - 2017-06-08 product_form extended (second parameter: 1...2) */
43 /* - 2017-08-08 new bode_plots, phase, absval, asymptotic */
44 /* - 2018-01-14 Nichols plot */
45 /* - 2018-02-24 Weird error with cabs,carg,assume_pos avoided */
46 /* - 2018-02-26 stable_area enhanced */
47 /* - 2018-03-16 bugfix in product_form, gain_margin and time_delay */
48 /* - 2018-12-03 (basic) treatment of time delays; function fullermap */
49 /* - 2018-12-16 "nilt" extended, new variable "try_symbolic_ilt" */
50 /* - 2019-01-03 "closed_loop" for time delayed systems */
51 /* - 2019-01-09 Digital algorithms added */
52 /* - 2019-01-28 "normalize" and "tf" added */
53 /* - 2019-02-24 "stable_ares" depicted as transparent "region" */
54 /* - 2019-03-26 "moving average" and "regan" added */
55 /* - 2019-05-08 "periodic" added */
56 /* - 2019-05-21 "add_noise", "mult_noise" added */
57 /* ======================================================================= */
59 disp("coma v.2.1 (Wilhelm Haager, 2019-05-21)")$
60 load("draw"); /* not necessary any more but beneficial (2013-11-16) */
62 /* ======================================================================= */
63 /* Some functions to avoid really weird errors (2018-02-24,2018-03-05) */
64 /* ======================================================================= */
65 xabs(x):=lambda([u],cabs(num(u))/cabs(denom(u)))(xthru(x));
66 xarg(x):=lambda([u],carg(num(u))-carg(denom(u)))(xthru(x));
69 /* ======================================================================= */
71 /* ======================================================================= */
75 coma_defaults:[grid=true, wx=true, dimensions=[700,400],
76 user_preamble="set grid lt 3 lc '#111111',lt 3 lc '#AAAAAA';set tics font 'courier new,10'",
77 color=[red,blue,dark-green,goldenrod,violet,gray40,dark-cyan,
78 orange,brown,sea-green],
79 fill_color=[red,blue,dark-green,goldenrod,violet,gray40,dark-cyan,
80 orange,brown,sea-green]
83 /* ======================================================================= */
85 /* ======================================================================= */
87 _COMA_msg[1]:"*** COMA error *** Coefficients must be numeric";
88 _COMA_msg[2]:"*** COMA error *** yrange must be a list of TWO ranges";
89 _COMA_msg[3]:"*** COMA warning *** Delay time ignored";
91 /* ======================================================================= */
93 /* ======================================================================= */
95 /* ----------------------------------------------------------------------- */
96 /* plot - plots functions in one and two variables (2015-10-24) */
97 /* ----------------------------------------------------------------------- */
100 [x,x1,x2,y,y1,y2,defs,hli,fli,h,n,t,wxx,varli,
101 wxplot_size:assoc(dimensions,coma_defaults),ratprint:false],
104 [x1,x2]:assoc('xrange,opts,[0,1]),
105 [y1,y2]:assoc('yrange,opts,[0,1]),
106 varli:map(lambda([u],if _COMA_go(u) then [t] else listofvars(u)),flatten([f])),
107 varli:map(lambda([u],delete(s,u)),varli),
108 varli:map(lambda([u],if u=[] then u:[t] else u),varli), /* 10/2015 fuer Treppenfunktion */
109 varli:map(sort,varli),
110 n:apply(max,map(length,varli)),
111 if n>2 then return(false),
113 if n<2 then block( /* 2d-plot */
114 fli:map(lambda([u,w],
115 if _COMA_go(u) then u else explicit(u,part(w,1),x1,x2)
117 else block( /* 3d-plot */
118 fli:map(lambda([u,w],
119 if _COMA_go(u) then u else explicit(u,part(w,1),x1,x2,part(w,2),y1,y2)
121 hli:_COMA_ppo(fli,defs,opts),
122 if is(n>1) then block(
123 if wxx#false then apply(wxdraw3d,hli) else apply(draw3d,hli))
125 if wxx#false then apply(wxdraw2d,hli) else apply(draw2d,hli))
128 /* ----------------------------------------------------------------------- */
129 /* step_response(f,opts) - plots the step response of f */
130 /* ----------------------------------------------------------------------- */
131 step_response(f,[opts]) :=
133 [wxplot_size:assoc(dimensions,coma_defaults),
134 hli,defs:[],fli,t,tanf,tend,wxx,ratprint:false,assume_pos:false],
136 if not listp(f) then f:[f],
137 fli:flatten(map(lambda([_u], if _COMA_go(_u) then _u
138 else if algorithmp(_u) then 'stepf(algorithm_step_response(_u))
139 else (nilt(xthru(_u/s),s,t))),f)),
140 fli:map(lambda([u],if _COMA_go(u) or algorithmp(u) then u
141 /* else explicit(if t>0 then u else 0,t,tanf,tend)),fli), */
142 else explicit(u,t,tanf,tend)),fli),
143 hli:_COMA_ppo(fli,defs,opts),
144 if option_exists('xrange,hli) then [tanf,tend]:get_option('xrange,hli)
145 else block([tanf,tend]:[0,_COMA_npv(_COMA_srat(sublist(f,ntfp)),[2,5,10])],
146 set_option('xrange=[tanf,tend],hli)),
147 if wxx#false then apply(wxdraw2d,ev(hli)) else apply(draw2d,ev(hli))
149 /* ----------------------------------------------------------------------- */
150 /* step_response(f,opts) - plots the step response of f (TESTVERSION 8.1.2019) */
151 /* ----------------------------------------------------------------------- */
152 step_response(f,[opts]) :=
154 [wxplot_size:assoc(dimensions,coma_defaults),
155 hli,defs:[],fli,t,tanf,tend,wxx,ratprint:false,assume_pos:false],
157 if not listp(f) then f:[f],
158 if option_exists('xrange,opts) then [tanf,tend]:get_option('xrange,opts)
159 else block([tanf,tend]:[0,_COMA_npv(_COMA_srat(sublist(f,ntfp)),[2,5,10])],
160 set_option('xrange=[tanf,tend],opts)),
161 fli:flatten(map(lambda([_u], if _COMA_go(_u) then _u
162 else if algorithmp(_u) then ('stepf(algorithm_step_response(_u,round(tend/ _u[2]))))
163 else (nilt( (_u/s),s,t))),f)),
164 fli:map(lambda([u],if _COMA_go(u) then u
165 /* else explicit(if t>0 then u else 0,t,tanf,tend)),fli), */
166 else explicit(u,t,tanf,tend)),fli),
167 hli:_COMA_ppo(fli,defs,opts),
168 if wxx#false then apply(wxdraw2d,ev(hli)) else apply(draw2d,ev(hli))
170 /* ----------------------------------------------------------------------- */
171 /* response(f,opts) - plots the inverse Laplace transform of f (pulse resp)*/
172 /* ----------------------------------------------------------------------- */
173 response(f,[opts]) :=
175 [wxplot_size:assoc(dimensions,coma_defaults),
176 hli,defs:[],fli,t,tanf,tend,wxx,ratprint:false,assume_pos:false],
179 fli:flatten(map(lambda([u], if _COMA_go(u) then u
180 else nilt(xthru(u),s,t)),f)),
181 fli:map(lambda([u],if _COMA_go(u) then u
182 else explicit(if t>0 then u else 0,t,tanf,tend)),fli),
183 hli:_COMA_ppo(fli,defs,opts),
184 if option_exists('xrange,hli) then [tanf,tend]:get_option('xrange,hli)
185 else block([tanf,tend]:[0,_COMA_npv(_COMA_srat(f),[2,5,10])],
186 set_option('xrange=[tanf,tend],hli)),
187 if wxx#false then apply(wxdraw2d,ev(hli)) else apply(draw2d,ev(hli))
190 /* ----------------------------------------------------------------------- */
191 /* nyquist_plot(f,opts) - nyquist plot of the transfer function f */
192 /* ----------------------------------------------------------------------- */
193 nyquist_plot(f,[opts]) :=
195 [wxplot_size:assoc(dimensions,coma_defaults)*[1,1.25],
196 hli,defs,t,tanf,tend,wxx,fli,ratprint:false],
198 defs:[aspect_ratio=-1, nticks=500],
199 [tanf,tend] : _COMA_bpr(sublist(flatten([f]),lambda([u],not _COMA_go(u)))),
200 [tanf,tend] : [tanf/10,tend*10], /* ADAPT RANGE HERE */
201 tanf:float(log(tanf)/log(10)),
202 tend:float(log(tend)/log(10)),
203 fli:map(lambda([u], if _COMA_go(u) then u else
204 [float(realpart(ev(u,s=%i*t))), float(imagpart(ev(u,s=%i*t)))]
206 fli:map(lambda([u],if _COMA_go(u) then u else ev(u,t=10**t)),fli),
207 fli:map(lambda([u],if _COMA_go(u) then u
208 else parametric(part(u,1),part(u,2),t,tanf,tend)),fli),
209 hli:ev(_COMA_ppo(fli,defs,opts)),
210 if wxx#false then apply(wxdraw2d,hli) else apply(draw2d,hli)
213 /* ----------------------------------------------------------------------- */
214 /* nichols_plot(f,opts) - Nichols plot of the Bode-diagram */
215 /* ----------------------------------------------------------------------- */
216 nichols_plot(f,[opts]) :=
218 [wxplot_size:assoc(dimensions,coma_defaults),hli,defs,t,tanf,tend,fli,
219 fli1,fli2,wxx,ratprint:false,db:false],
221 if option_exists('logy,opts) and assoc('logy,opts)='dB then /* dB-scale, 2017-05-07 */
223 set_option('logy=false,opts),
226 fli:ratsimp(flatten([f])),
227 [tanf,tend] : _COMA_bpr(sublist(flatten([f]),lambda([u],not _COMA_go(u)))),
228 [tanf,tend] : [tanf/10,tend*10], /* ADAPT RANGE HERE */
229 tanf:float(log(tanf)/log(10)),
230 tend:float(log(tend)/log(10)),
231 fli1:map(lambda([u],if _COMA_go(u) then u else
232 float(if db then (20*log(absval(u))/log(10)) else (absval(u)))),fli),
233 fli2:map(lambda([u], if _COMA_go(u) then u else ev(phase(u))),fli),
234 fli1:map(lambda([u],if _COMA_go(u) then u else ev(u,omega=10**t)),fli1),
235 fli2:map(lambda([u],if _COMA_go(u) then u else ev(u,omega=10**t)),fli2),
236 fli:map(lambda([u,v],if _COMA_go(u) then u else
237 parametric(u,v,t,tanf,tend)),fli2,fli1),
238 defs:['nticks=500,'logy=true,grid=false,'user_preamble="set grid xtics mxtics ytics mytics"],
239 hli:_COMA_ppo(fli,defs,opts),
240 if wxx#false then apply(wxdraw2d,ev(hli)) else apply(draw2d,ev(hli))
243 /* ----------------------------------------------------------------------- */
244 /* magnitude_plot(f,opts) - magnitude plot of the Bode-diagram */
245 /* ----------------------------------------------------------------------- */
246 magnitude_plot(f,[opts]) :=
248 [wxplot_size:assoc(dimensions,coma_defaults),hli,defs,t,tanf,tend,fli,wxx,ratprint:false,db:false],
250 if option_exists('logy,opts) and assoc('logy,opts)='dB then /* dB-scale, 2017-05-07 */
252 set_option('logy=false,opts),
255 fli:ratsimp(flatten([f])),
256 fli:map(lambda([u],if _COMA_go(u) then u else
257 float(if db then (20*log(absval(u))/log(10)) else (absval(u)))),fli),
258 fli:map(lambda([u],if _COMA_go(u) then u else explicit(u,omega,tanf,tend)),fli),
259 defs:['logx=true,'logy=true,grid=false,'user_preamble="set grid xtics mxtics ytics mytics"],
260 hli:_COMA_ppo(fli,defs,opts),
261 if option_exists('xrange,opts) then [tanf,tend]:assoc('xrange,opts)
262 else block([tanf,tend] : _COMA_bpr(f),
263 set_option('xrange=[tanf,tend],hli)),
264 if wxx#false then apply(wxdraw2d,ev(hli)) else apply(draw2d,ev(hli))
267 /* ----------------------------------------------------------------------- */
268 /* phase_plot(f,opts) - phase plot of the Bode-diagram */
269 /* ----------------------------------------------------------------------- */
270 phase_plot(f,[opts]) :=
272 [wxplot_size:assoc(dimensions,coma_defaults),
273 hli,defs,t,tanf,tend,fli,ratprint:false,ph,wxx,omega],
275 defs:['logx=true,grid=false,'user_preamble="set grid xtics mxtics ytics mytics",'nticks=500],
276 fli:map(ratsimp,flatten([f])),
277 fli:map(lambda([u], if _COMA_go(u) then u else ev(phase(u),assume_pos=true)),fli),
278 fli:map(lambda([u], if _COMA_go(u) then u else explicit(u,omega,tanf,tend)),fli),
279 hli:_COMA_ppo(fli,defs,opts),
280 if option_exists('xrange,opts) then [tanf,tend]:assoc('xrange,opts)
282 [tanf,tend] : _COMA_bpr(f),
283 set_option('xrange=[tanf,tend],hli)),
284 if wxx#false then apply(wxdraw2d,ev(hli)) else apply(draw2d,ev(hli))
287 /* ----------------------------------------------------------------------- */
288 /* bode_plot(f,opts) - Bode-diagram (magnitude_plot and phase_plot) */
289 /* ----------------------------------------------------------------------- */
290 bode_plot(f,[opts]) :=
292 [wxplot_size:assoc(dimensions,coma_defaults)*[1,2],
293 fli,fli1,fli2,hli1,hli2,defs1,defs2,t,tanf,tend,ratprint:false,wxx,
294 xdim,ydim,dims,g0,db:false],
296 if option_exists('logy,opts) and assoc('logy,opts)='dB then /* dB-scale, 2017-05-07 */
298 set_option('logy=false,opts),
301 fli:ratsimp(flatten([f])),
302 defs1:['logx=true,'logy=true,grid=false,'user_preamble=["set tmargin 0.5",
303 "set lmargin 6","set rmargin 3","set bmargin 1.5","set grid xtics mxtics ytics mytics"]],
304 defs2:['logx=true, 'nticks=500,grid=false,'user_preamble=["set tmargin 0.5",
305 "set lmargin 6","set rmargin 3","set bmargin 1.5","set grid xtics mxtics ytics mytics"]],
306 xdim:600, ydim:500, /* default values (for both diagrams together) */
307 if listp(coma_defaults)=false then coma_defaults:['grid=true],
308 if option_exists('dimensions,coma_defaults) then block(
309 xdim:assoc('dimensions,coma_defaults)[1],
310 ydim:2.0*assoc('dimensions,coma_defaults)[2]),
311 if option_exists('dimensions,opts) then block(
312 xdim:assoc('dimensions,opts)[1],
313 ydim:assoc('dimensions,opts)[2]),
314 dims:'dimensions=[xdim,ydim],
315 /* Generating the function list */
316 fli1:map(lambda([u],if _COMA_go(u) then u else
317 float(if db then (20*log(absval(u))/log(10)) else (absval(u)))),fli),
318 fli1:map(lambda([u],if _COMA_go(u) then u else explicit(u,omega,tanf,tend)),fli1),
319 hli1:_COMA_ppo(fli1,defs1,opts),
320 delete_option('dimensions,hli1),
321 fli2:map(lambda([u], if _COMA_go(u) then u else ev(phase(u))),fli),
322 fli2:map(lambda([u], if _COMA_go(u) then u else explicit(u,omega,tanf,tend)),fli2),
323 hli2:_COMA_ppo(fli2,defs2,opts),
324 delete_option('dimensions,hli2),
325 /* Processing the ranges (xrange, yrange) */
326 if option_exists('xrange,opts) then [tanf,tend]:assoc('xrange,opts)
328 [tanf,tend] : _COMA_bpr(f),
329 set_option('xrange=[tanf,tend],hli1),
330 set_option('xrange=[tanf,tend],hli2)),
331 if option_exists('yrange,hli1) then block([yr],
332 yr:assoc('yrange,opts),
333 delete_option('yrange,hli1), delete_option('yrange,hli2),
334 if listp(yr) and listp(yr[1]) and listp(yr[2]) then block(
335 set_option('yrange=yr[1],hli1), set_option('yrange=yr[2],hli2))
336 else disp(_COMA_msg[2])),
337 /* Put the scenes together */
338 g0:[apply(gr2d,ev(hli1)),apply(gr2d,ev(hli2)),dims],
339 if wxx#false then apply(wxdraw,g0) else apply(draw,g0)
342 /* ----------------------------------------------------------------------- */
343 /* absval - absolute value of a frequency response (treats asymptotic) */
344 /* ----------------------------------------------------------------------- */
346 block([fli,ratprint:false,assume_pos:true,dt],
347 /* --- treatment of asymptotic, 24.7.2017 --- */
348 [fli,dt]:_COMA_strip(f),
350 if not mapatom(u) and op(u)='asymptotic then block
351 ([assume_pos:true,inflag:true,f1,zz,nn,zl,nl,hz,hn,n,l0,l1],
352 f1:product_form(first(args(u))),
355 zl:if not mapatom(zz) and op(zz)="*" then args(zz) else [zz],
356 nl:if not mapatom(nn) and op(nn)="*" then args(nn) else [nn],
357 hz:map(lambda([u],cl:coefficient_list(u,s),
359 [l0,l1]:[first(cl),last(cl)],
360 if l0 # 0 then (if n<2 or omega<1/abs(l1)^(1/(n-1))
361 then l0 else s^(n-1)*l1)
362 else s^(n-1)*l1),zl),
363 hn:map(lambda([u],cl:coefficient_list(u,s),
365 [l0,l1]:[first(cl),last(cl)],
366 if l0 # 0 then (if n<2 or omega<1/abs(l1)^(1/(n-1))
367 then l0 else s^(n-1)*l1)
368 else s^(n-1)*l1),nl),
369 apply("*",hz)/apply("*",hn)
372 /* --- END treatment of asymptotic --- */
373 fli:float(map(xabs,subst(s=%i*omega,fli))),
374 if length(fli) = 1 then fli[1] else fli)$
376 /* ----------------------------------------------------------------------- */
377 /* phase(f) - phase of a frequency response (treats asymptotic) */
378 /* ----------------------------------------------------------------------- */
380 [inflag:true,n,li,ass:false,res,ratprint:false,assume_pos:true,dt],
381 [f,dt]:_COMA_strip(f),
383 if not mapatom(ff) and op(ff)='asymptotic then (ass:true, ff:product_form(first(args(ff))))
384 else ff:product_form(ff),
385 if not mapatom(ff) and op(ff)="*" then li:args(ff) else li:[ff],
387 while n<length(li) do /* Zerlegen von Potenzen */
390 if not mapatom(li[n]) and op(li[n])="^" and abs(args(li[n])[2])>1 then
391 block([prae,post,mid],
392 prae:rest(li,-length(li)+n-1),
394 mid:makelist(args(li[n])[1]**signum(args(li[n])[2]),k,1,abs(args(li[n])[2])),
395 li:append(prae,mid,post)
398 /* --- treatment of asymptotic 24.7.2017 --- */
400 if ass then block([lu,lo,xcf:_COMA_cf(u)],
401 if length(xcf)>0 then
402 ( lu:limit(xarg(subst(s=%i*omega,u)),omega,0,plus),
403 lo:limit(xarg(subst(s=%i*omega,u)),omega,inf),
404 if omega<xcf[1] then lu else lo) else xarg(subst(s=%i*omega,u)) )
405 else xarg(subst(s=%i*omega,u))
407 /* --- END treatment of asymptotic --- */
408 apply("+", li)*180/%pi),
409 f) + log(dt)/s*omega*180/%pi,
410 if length(res) = 1 then res[1] else res)$
412 /* ----------------------------------------------------------------------- */
413 /* poles_and_zeros(f,opts) - plots the poles/zeros distribution */
414 /* ----------------------------------------------------------------------- */
415 poles_and_zeros(f,[opts]) :=
417 [wxplot_size:assoc(dimensions,coma_defaults),dt,
418 hli,defs,zli,pli,wxx,x1,x2,y1,y2,range,ratprint:false],
420 defs:[aspect_ratio=-1,points_joined=false],
421 [f,dt]:_COMA_strip(f), /* _COMA_(u)...Wrapper around graphic object */
422 zli:map(lambda([u],if _COMA_go(u) then _COMA_(u) else zeros(u)),f),
423 pli:map(lambda([u],if _COMA_go(u) then _COMA_(u) else poles(u)),f),
424 range:_COMA_rlpr(flatten(sublist(append(zli,pli),listp)),1.2,1.2),
425 [x1,x2]:assoc('xrange,opts,rhs(range[1])),
426 [y1,y2]:assoc('yrange,opts,rhs(range[2])),
427 set_option('xrange=[x1,x2],opts),
428 set_option('yrange=[y1,y2],opts),
429 zli:map(lambda([u],if op(u)=_COMA_ then u
430 else map(lambda([v],[realpart(v),imagpart(v)]),u)),zli),
431 zli:map(lambda([u],if op(u)=_COMA_ then u else [points(u)]),zli),
432 zli:map(lambda([u],if op(u)=_COMA_ then u else cons(point_type=6,u)),zli),
433 pli:map(lambda([u],if op(u)=_COMA_ then u
434 else map(lambda([v],[realpart(v),imagpart(v)]),u)),pli),
435 pli:map(lambda([u],if op(u)=_COMA_ then u else [points(u)]),pli),
436 pli:map(lambda([u],if op(u)=_COMA_ then u else cons(point_type=2,u)),pli),
437 pli:map(lambda([u,v],if op(u)=_COMA_ then args(u)
438 else append(u,v)),zli,pli),
439 /* else flatten([u,v])),zli,pli), */
440 hli:_COMA_ppo(pli,defs,opts),
441 hli:delete(points([]),flatten(hli)),
442 if wxx#false then apply(wxdraw2d,hli) else apply(draw2d,hli)
445 /* ----------------------------------------------------------------------- */
447 /* ----------------------------------------------------------------------- */
448 contourplot(f,p1,p2,[opts]):=
450 [wxplot_size:assoc(dimensions,coma_defaults),
451 x1,x2,y,y1,y2,defs,cli,hli,fli,ratprint:false,wxx],
453 defs:[contours=[0],color=[red],line_width=[1]],
454 hli:_COMA_ppo(1,defs,opts),
455 [x1,x2]:assoc('xrange,opts,[0,1]),
456 [y1,y2]:assoc('yrange,opts,[0,1]),
457 cli:assoc('contours,hli),
458 hli:map(lambda([u],f=u),cli),
459 hli:map(lambda([u],implicit(u,p1,x1,x2,p2,y1,y2)),hli),
460 hli:flatten(_COMA_ppo(hli,defs,opts)),
461 delete_option('contours,hli),
462 if wxx#false then apply(wxdraw2d,hli) else apply(draw2d,hli)
465 /* ----------------------------------------------------------------------- */
466 /* root_locus(f,opts) - root locus plot of the transfer function f */
467 /* ----------------------------------------------------------------------- */
468 root_locus(f,[opts]) :=
470 [wxplot_size:assoc(dimensions,coma_defaults),
471 i,range,nnticks,fac,pars,nn,pl,poli:[],gpoli:[],t,t1,t2,defs,hli,fli,h,
472 ratprint:false,startpoints,endpoints,wxx,dt],
474 defs:[aspect_ratio=-1,trange=[0.001,100],nticks=500],
475 hli:_COMA_ppo(0,defs,opts),
476 [t1,t2]:assoc('trange,hli),
477 nnticks:assoc('nticks,hli),
478 delete_option(nticks,hli),
479 delete_option(trange,hli),
480 fac:float((t2/t1)**(1/nnticks)),
481 [fli,dt]:_COMA_strip(f),
482 pars:map(lambda([u],first(delete(s,listofvars(u)))),fli), /* variable */
484 flatten(map(lambda([u,v],poles(ev(u,ev(v)=t1))),fli,pars)),2,1.5),
485 if not option_exists('xrange,hli) then set_option(first(range),hli),
486 if not option_exists('yrange,hli) then set_option(last(range),hli),
487 for i:1 step 1 thru length(fli) do block(
488 nn:denom(ratsimp(fli[i])),
489 poli:[zeros(ev(nn,ev(pars[i])=t1))],
493 poli:endcons(_COMA_rlsp(zeros(ev(nn,ev(pars[i])=t)),last(poli)),poli)),
494 poli:map(lambda([v],map(lambda([u],[realpart(u),imagpart(u)]),v)),poli),
495 [startpoints,endpoints]:[first(poli),last(poli)],
496 poli:map('points,apply("[",args(transpose(apply(matrix,poli))))),
497 poli:append(['points_joined=true,'point_type=-1],poli),
498 poli:append(poli,['points_joined=false,'point_type=2,points(startpoints)]),
499 poli:append(poli,['point_type=6,points(endpoints)]),
500 gpoli:endcons(poli,gpoli)
502 hli:_COMA_ppo(gpoli,hli,[]),
503 if wxx#false then apply(wxdraw2d,ev(hli)) else apply(draw2d,ev(hli))
505 _COMA_rlpr(poles,fx,fy) := /* root locus plot range */
506 block( /* fx,fy ... oversizing factors */
507 [immax,remin,remax,xmin,xmax,ymax,xm,aspect_ratio:0.6],
508 immax:apply(max,imagpart(poles)),
509 remax:apply(max,realpart(poles)),
510 remin:apply(min,realpart(poles)),
513 xmax:xm+fx*(remax-remin)/2,
514 xmin:xm-fx*(remax-remin)/2,
515 if is(xmax=xmin) then block(xmax:xmax+1,xmin:xmin-1),
516 ymax:max(ymax,(xmax-xmin)/2*aspect_ratio),
517 xmax:max(xmax,xm+ymax/aspect_ratio),
518 xmin:min(xmin,xm-ymax/aspect_ratio),
519 ['xrange=[xmin,xmax],'yrange=[-ymax,ymax]]
521 _COMA_rlsp(z1,z2) := /* root locus sort points */
522 block([l:z1], for i:1 step 1 thru length(z1-1) do block(
523 for j:i+1 step 1 thru length(z1) do
524 if cabs(z2[i]-l[j])<cabs(z2[i]-l[i]) then [l[j],l[i]]:[l[i],l[j]]),l)$
526 /* ======================================================================= */
527 /* Stability and related functions */
528 /* ======================================================================= */
530 /* ----------------------------------------------------------------------- */
531 /* poles(f) - poles of the transfer function f(s) */
532 /* ----------------------------------------------------------------------- */
534 block([res,polyfactor:false,ratprint:false,dt],
536 if ntfp(ff) then map(rhs,chop(allroots(%i*expand(float(denom(ff))))))
538 ), xthru(first(_COMA_strip(float(f))))),
539 if listp(f) then res else res[1]
542 /* ----------------------------------------------------------------------- */
543 /* zeros(f) - zeros of the transfer function f(s) */
544 /* ----------------------------------------------------------------------- */
546 block([res,polyfactor:false,ratprint:false,dt],
548 if ntfp(ff) then map(rhs,chop(allroots(%i*expand(float(num(ff))))))
549 else []),xthru(first(_COMA_strip(float(f))))),
550 if listp(f) then res else res[1]
553 /* ----------------------------------------------------------------------- */
554 /* hurwitz(p) - Hurwitz-determinants of the polynomial p(s) */
555 /* ----------------------------------------------------------------------- */
558 [cli,n,i,k,hli,mat,res],
559 if not listp(p) then p:[p],
565 cli:reverse(coefficient_list(pp,s)),
566 cli:append(makelist(0,i,1,n-1),cli,makelist(0,i,1,n )),
567 hli:makelist(mat:apply(matrix,makelist(
568 /* makelist('cli[''((2*(n-j)+1)+(i-1))],j,1,k),i,1,k)) ,k,2,n), HAA, 14.2.2018 */
569 makelist('cli[((2*(n-j)+1)+(i-1))],j,1,k),i,1,k)) ,k,2,n),
570 hli:ev(hli,nouns), /* trick to prevent substituting variables k and n */
571 hli:map(determinant,hli),
572 if n>1 then hli else [])
574 if length(res)=1 then res[1] else res
577 /* ----------------------------------------------------------------------- */
578 /* stable_area(f,p1,p2) - plots the stable area with respect to p1 and p2 */
579 /* ----------------------------------------------------------------------- */
580 stable_area_outlined(f,p1,p2,[opts]):=
582 [wxplot_size:assoc(dimensions,coma_defaults),
583 x,x1,x2,y,y1,y2,defs,hli,fli,h,wxx,ratprint:false],
585 defs:[xrange=[0,1],yrange=[0,1]],
587 h:map(lambda([u],if _COMA_go(u) then u else append(hurwitz(denom(u)),
588 coefficient_list(denom(u)))),f),
589 fli:map(lambda([u],if _COMA_go(u) then u else apply('min,u)),h),
590 fli:map(lambda([u],if _COMA_go(u) then u else implicit(u,p1,x1,x2,p2,y1,y2)),fli),
591 hli:_COMA_ppo(fli,defs,opts),
592 [x1,x2]:get_option('xrange,hli),
593 [y1,y2]:get_option('yrange,hli),
594 if wxx#false then apply(wxdraw2d,ev(hli)) else apply(draw2d,ev(hli))
597 stable_area(f,p1,p2,[opts]):= /* depiction as region 24.2.2019 */
599 [wxplot_size:assoc(dimensions,coma_defaults),
600 x,x1,x2,y,y1,y2,defs,hli,fli,h,wxx,ratprint:false],
602 defs:[xrange=[0,1],yrange=[0,1],x_voxel=50,y_voxel=50,
603 user_preamble="set style fill transparent solid 0.2 noborder"],
605 h:map(lambda([u],if _COMA_go(u) then u else
606 map(lambda([v],v>0),append(hurwitz(denom(u)),coefficient_list(denom(u))))),f),
607 fli:map(lambda([u],if _COMA_go(u) then u else apply("and",u)),h),
608 fli:map(lambda([u],if _COMA_go(u) then u else region(u,p1,x1,x2,p2,y1,y2)),fli),
609 hli:_COMA_ppo(fli,defs,opts),
610 [x1,x2]:get_option('xrange,hli),
611 [y1,y2]:get_option('yrange,hli),
612 if wxx#false then apply(wxdraw2d,ev(hli)) else apply(draw2d,ev(hli))
615 unstable_area(f,p1,p2,[opts]):= /* depiction as region 24.2.2019 */
617 [wxplot_size:assoc(dimensions,coma_defaults),
618 x,x1,x2,y,y1,y2,defs,hli,fli,h,wxx,ratprint:false],
620 defs:[xrange=[0,1],yrange=[0,1],x_voxel=50,y_voxel=50,
621 user_preamble="set style fill transparent solid 0.2 noborder"],
623 h:map(lambda([u],if _COMA_go(u) then u else
624 map(lambda([v],v<0),append(hurwitz(denom(u)),coefficient_list(denom(u))))),f),
625 fli:map(lambda([u],if _COMA_go(u) then u else apply("or",u)),h),
626 fli:map(lambda([u],if _COMA_go(u) then u else region(u,p1,x1,x2,p2,y1,y2)),fli),
627 hli:_COMA_ppo(fli,defs,opts),
628 [x1,x2]:get_option('xrange,hli),
629 [y1,y2]:get_option('yrange,hli),
630 if wxx#false then apply(wxdraw2d,ev(hli)) else apply(draw2d,ev(hli))
633 /* ----------------------------------------------------------------------- */
634 /* stablep(f) - checks whether the transfer function f(s) is stable */
635 /* ----------------------------------------------------------------------- */
637 block([ratprint:false],
640 not (member(true,map(lambda([u],is(realpart(u)>=0)),poles(ff))))
642 else not (member(true,map(lambda([u],is(realpart(u)>=0)),poles(f))))
645 /* ----------------------------------------------------------------------- */
646 /* phase_crossover(f) - calculates the phase crossover frequencies of f */
647 /* ----------------------------------------------------------------------- */
649 block([re,im,sol,res,ratprint:false,omega,dt],
650 [f,dt]:_COMA_strip(f),
651 res:map(lambda([ff,dd],
652 if dd=1 then block( /* without time delay */
653 re:realpart(ev(ff,s=%i*omega)),
654 im:imagpart(ev(ff,s=%i*omega)),
655 sol:algsys([num(im)=0],[omega]),
656 sol:sublist(flatten(sol),lambda([u],subst(u,denom(re))#0 and
657 is(ev(re,u)<0) and is(rhs(u)>0))),
658 sort(sol,lambda([u,v],rhs(u)<rhs(v))))
659 else block([], /* with time delay */
660 ['omega=find_root(phase(ff*dd)+180,omega,0,1e10)])
662 if length(res)=1 then res[1] else res
665 /* ----------------------------------------------------------------------- */
666 /* gain_crossover(f) - calculates the gain crossover frequencies of f */
667 /* ----------------------------------------------------------------------- */
670 [roots,sol,res,polyfactor:false,ratprint:false,zz,omega,dt],
671 [f,dt]:_COMA_strip(f),
672 res:map(lambda([ff,dd],
676 ff:xthru(subst(s=%i*omega,ff)),
677 zz:num(xthru((cabs(num(ff))/cabs(denom(ff)))**2-1)),
678 roots:if hipow(zz,omega)>0 then realroots(zz) else [],
679 sol:float(sublist(roots,lambda([u],is(part(u,2)>0)))),
680 if listp(sol) then sort(sol,lambda([u,v],rhs(u)<rhs(v)))
683 if length(res)=1 then res[1] else res
686 /* ----------------------------------------------------------------------- */
687 /* phase_margin(f) - calculates the phase margin of f */
688 /* ----------------------------------------------------------------------- */
691 [res,h,ratprint:false],
692 if not listp(f) then f:[f],
694 h:gain_crossover(ff),
695 180+map(lambda([v],float(ev(phase(ff),v))),h)),f),
696 if length(res)=1 then res[1] else res
699 /* ----------------------------------------------------------------------- */
700 /* gain_margin(f) - calculates the gain margin of f */
701 /* ----------------------------------------------------------------------- */
703 block([res,h,ratprint:false,omega],
704 if not listp(f) then f:[f],
706 h:float(phase_crossover(ff)),
707 (-1)/realpart(map(lambda([v],ev(ff,s=%i*omega,v,eval)),h))),f),
708 if length(res)=1 then res[1] else res
711 /* ----------------------------------------------------------------------- */
712 /* stability_limit(f,par) - calculates conditions for imaginary poles */
713 /* ----------------------------------------------------------------------- */
714 stability_limit(f,par,[opt]):=
716 [eqs,nn,sol,res,assume_pos:true,omega,ratprint:false,dt],
717 [f,dt]:_COMA_strip(f),
718 if setify(dt)#{1} then disp(_COMA_msg[3]),
721 nn:ev(denom(ff),s=%i*omega),
722 eqs:map(lambda([u],u(nn)=0),[realpart,imagpart]),
723 sol:ev(solve(eqs,[par,omega]),realonly=true),
724 sol:sublist(sol,lambda([u], assoc(omega,u)>0)))
726 if length(res)=1 then res[1] else res
729 /* ----------------------------------------------------------------------- */
730 /* damping(f) - negative realpart of the rightmost pole of f(s) */
731 /* ----------------------------------------------------------------------- */
734 [polyfactor:true,ratprint:false,expr,p,res],
737 p:poles(float(xthru(ff))),
738 expr:map(realpart,p),
740 ), first(_COMA_strip(f))),
741 if length(res)=1 then res[1] else res
744 /* ----------------------------------------------------------------------- */
745 /* damping_ratio(f) - minimal value of all damping ratios */
746 /* ----------------------------------------------------------------------- */
749 [polyfactor:true,ratprint:false,expr,p,res],
752 p:poles(float(xthru(ff))),
753 expr:map(lambda([u],realpart(u)/sqrt(realpart(u)**2+imagpart(u)**2)),p),
755 ), first(_COMA_strip(f))),
756 if length(res)=1 then res[1] else res
759 /* ======================================================================= */
760 /* Transfer Function Related */
761 /* ======================================================================= */
763 /* ----------------------------------------------------------------------- */
764 /* normalize(f,a) - applies the similarity theorm on a transfer functi */
765 /* ----------------------------------------------------------------------- */
767 block([alpha:1,k,cl,res,assume_pos:true],
768 if length(_a)>0 then alpha: _a[1],
769 if not listp(f) then f:[f],
772 cl:coefficient_list(denom(u),s),
773 k:last(cl)**(1/(length(cl)-1)),
774 subst(s=s/alpha/k,u)),f),
775 res:fullmap(lambda([u],if numberp(u) and abs('round(u)-u) < 1.0e-10
776 and round(u)>0 then 'round(u) else u),sum_form(res)),
777 if length(res)=1 then res[1] else res)$
779 /* ----------------------------------------------------------------------- */
780 /* tf(type,a,..) - generates special transferfunctions */
781 /* ----------------------------------------------------------------------- */
784 block([k,T1,T2,D,omega_n,Td,Tn,Tv,Ti,kr,Ta,Tb,nz:1,nn:2,h,pz,pn,c,d,eps,a,b],
787 (if length(p)>0 then k:p[1],if length(p)>1 then T1:p[2],return(k/(1+s*T1))),
788 if f='pt2 and length(p)>0 and p[1]='prod then
789 (if length(p)>1 then k:p[2],if length(p)>2 then T1:p[3], if length(p)>3 then T2:p[4],
790 return(k/((1+s*T1)*(1+s*T2)))),
792 (if length(p)>0 then k:p[1],if length(p)>1 then D:p[2],if length(p)>2 then omega_n:p[3],
793 return(k/(1+2*D/omega_n*s+s**2/omega_n**2))),
795 (if length(p)>0 then Td:p[1],if length(p)>1 then T1:p[2],
796 return(s*Td/(1+s*T1))),
798 (if length(p)>0 then Ti:p[1],if length(p)>1 then T1:p[2],
799 return(1/(s*Ti*(1+s*T1)))),
801 (if length(p)>0 then k:p[1],if length(p)>1 then T1:p[2],if length(p)>2 then T2:p[3],
802 return(k*(1+s*T1)/(1+s*T2))),
804 (if length(p)>0 then k:p[1],if length(p)>1 then T1:p[2],
807 (if length(p)>0 then k:p[1],if length(p)>1 then T1:p[2],
808 return(k*(1-s*T1)/(1+s*T1))),
810 (if length(p)>0 then kr:p[1],if length(p)>1 then Tn:p[2],
811 return(kr*(1+1/(s*Tn)))),
812 if f='pid and length(p)>0 and p[1]='prod then
813 (if length(p)>1 then Ta:p[2],if length(p)>2 then Tb:p[3], if length(p)>3 then Ti:p[4],
814 return((1+s*Ta)*(1+s*Tb)/(s*Ti))),
816 (if length(p)>0 then kr:p[1],if length(p)>1 then Tn:p[2],if length(p)>2 then Tv:p[3],
817 return(kr*(1+1/(s*Tn)+s*Tv))),
818 if f='pidt1 and length(p)>0 and p[1]='prod then
819 (if length(p)>1 then Ta:p[2],if length(p)>2 then Tb:p[3], if length(p)>3 then Ti:p[4],
820 if length(p)>4 then T1:p[5],return((1+s*Ta)*(1+s*Tb)/(s*Ti*(1+s*T1)))),
822 (if length(p)>0 then kr:p[1],if length(p)>1 then Tn:p[2],if length(p)>2 then Tv:p[3],
823 if length(p)>3 then T1:p[4], return(kr*(1+1/(s*Tn)+s*Tv/(1+s*T1)))),
825 (if length(p)>0 then nz:p[1],if length(p)>1 then nn:p[2],if length(p)>2 then c:p[3],
826 if length(p)>3 then d:p[4],return(sum(c[i]*s**i,i,0,nz)/sum(d[i]*s**i,i,0,nn))),
830 if length(p)>1 and map(numberp,rest(p,length(p)-2))=[true,true]
831 then (h:last(p),nn:second(reverse(p)))
832 else if length(p)>0 and numberp(last(p)) then (nn:last(p)),
834 if member('normalized,p) then
837 for i:1 thru nn-1 do pn: pn + (1+random(h)) * s**i,
838 for i:1 thru nz do pz: pz + (1+random(h)) * s**i
843 for i:0 thru nn do pn: pn + (1+random(h)) * s**i,
844 for i:0 thru nz do pz: pz + (1+random(h)) * s**i
847 while not stablep(f) and member('stable,p) do
849 if member('normalized,p) then (pn:1+s**nn,for i:1 thru nn-1 do pn: pn+(1+random(h))*s**i)
850 else (pn:0,for i:0 thru nn do pn: pn + (1+random(h))*s**i),
854 if f='butterworth then
856 nn:if length(p)>0 then p[1] else 3,
857 h:if length(p)>1 then p[2] else 1,
858 return(float(h**nn/([1,s+h,1][1+mod(nn,2)]*
859 product(expand((s**2-2*s*h*cos((2*k+nn-1)/(2*nn)*%pi)+h**2)),k,1,nn/2))))
861 if f='pseudobutt then
863 nn:if length(p)>0 then p[1] else 3, h:if length(p)>1 then p[2] else 1,
864 return(float(h**nn/([1,s+h,1][1+mod(nn,2)]*
865 product(expand((s+h*(sin(k*%pi/(nn+1))+%i*cos(k*%pi/(nn+1))))*
866 (s+h*(sin(k*%pi/(nn+1))-%i*cos(k*%pi/(nn+1))))),k,1,nn/2))))
868 if f='ptn then /* similar to butterworth, poles confined to a given angle */
870 nn:if length(p)>0 then p[1] else 3,
871 h:if length(p)>1 then p[2] else 1,
872 eps:if length(p)>2 then p[3] else 0,
873 if evenp(nn) then return(float(h**nn/(product(expand(
874 (s**2+2*s*h*cos((k-1/2)*%pi/(nn-1)*eps/90)+h**2)),k,1,nn/2))))
875 else return(float(h**nn/((h+s)*product(expand(
876 (s**2+2*s*h*cos(k/(nn-1)*%pi*eps/90)+h**2)),k,1,nn/2))))
878 if f='chebychev or f='tschebytscheff then
880 nn:if length(p)>0 then p[1] else 3,
881 h:if length(p)>1 then p[2] else 1,
882 eps:if length(p)>2 then p[3] else 0.2,
885 then 1/float(product(
886 ( b:1/(cosh(eps)**2-cos((2*k-1)*%pi/(2*nn))**2),
887 a:2*b*sinh(eps)*cos((2*k-1)*%pi/(2*nn)),
888 1+a*s+b*s**2),k,1,nn/2))
889 else 1/float((1+s/sinh(eps)) * product(
890 ( b:1/(cosh(eps)**2-cos((k-1)*%pi/nn)**2),
891 a:2*b*sinh(eps)*cos((k-1)*%pi/nn),
892 1+a*s+b*s**2),k,2,(nn+1)/2))))
896 nn:if length(p)>0 then p[1] else 3,
897 h:if length(p)>1 then p[2] else 1,
898 return(normalize(1/(1+sum(product(2*(nn-i+1)/i/(2*nn-i+1),i,1,k)*(s)**k,k,1,nn)),h))
902 nn:if length(p)>0 then p[1] else 3,
903 h:if length(p)>1 then p[2] else 1,
904 return(normalize(1/sum(binomial(nn-floor((k+1)/2),floor(k/2))*s^(nn-k),k,0,nn),h))
908 /* ----------------------------------------------------------------------- */
909 /* tranftype(f) - returns the type of the transfer function f as a string */
910 /* ----------------------------------------------------------------------- */
915 [zz,nn,z0:1,z1,n0:1,n1,ratprint:false],
916 zz:coefficient_list(num(xthru(ff)),s),
917 nn:coefficient_list(denom(xthru(ff)),s),
920 while is(zz[z0]=0) do z0:z0+1,
921 while is(nn[n0]=0) do n0:n0+1,
922 if is([z1,n1]=[1,1]) then return("P"),
923 if is([z1,n0]=[1,1]) then return(concat("PT",n1-1)),
924 if is([z1,n1]=[1,2]) then return(concat("I")),
925 if is([z1,n0]=[1,n1]) then return(concat("I",n1-1)),
926 if is([z1,n0]=[1,2]) then return(concat("IT",n1-n0)),
927 if is([z1]=[1]) then return(concat("I",n0-1,"T",n1-n0)),
928 if is([z0,z1,n1]=[2,2,1]) then return("D"),
929 if is([z0,n1]=[z1,1]) then return(concat("D",z0-1)),
930 if is([z0,z1,n0]=[2,2,1]) then return(concat("DT",n1-1)),
931 if is([z1,n1]=[2,1]) then return("PD"),
932 if is([z0,n1]=[1,1]) then return(concat("PD",z1-1)),
933 if is([z0,z1,n0]=[1,2,1]) then return(concat("PDT",n1-1)),
934 if is([n1]=[1]) then return(concat("D",n0-1,"T",n1-n0)),
935 if is([z1,n0,n1]=[2,2,2]) then return("PI"),
936 if is([z1,n0,n1]=[3,2,2]) then return("PID"),
937 if is([z1,n0]=[3,2]) then return(concat("PIDT",n1-n0)),
938 if is([z0,z1,n1]=[1,2,1]) then return("PD"),
939 if is([z0,n0]=[1,1]) then return(concat("PD",z1-z0,"T",n1-n0)),
941 ), first(_COMA_strip(f))),
942 if length(res)=1 then res[1] else res
944 tftype(f):=tranftype(f)$
946 /* ----------------------------------------------------------------------- */
947 /* rantranf(n) - Random transfer function of order n */
948 /* ----------------------------------------------------------------------- */
951 [zz:0, nn:0, kz:random(n), kn:n, i, f],
952 for i:0 step 1 thru kn do nn: nn + (1+random(10)) * s**i,
953 for i:0 step 1 thru kz do zz: zz + (1+random(10)) * s**i,
957 /* ----------------------------------------------------------------------- */
958 /* stable_rantranf(n) - stable random transfer function of order n */
959 /* ----------------------------------------------------------------------- */
961 stable_rantranf(n) :=
963 [ntest:1,zz:0, nn:0, kz, kn, i, f],
966 for i:0 step 1 thru kn do nn: nn + (1+random(10)) * s**i,
967 for i:0 step 1 thru kz do zz: zz + (1+random(10)) * s**i,
969 unless stablep(f) do block(
972 for i:0 step 1 thru kn do nn: nn + (1+random(10)) * s**i,
977 /* ----------------------------------------------------------------------- */
978 /* gentranf(c,nz,d,nn) - general transfer function of order nn */
979 /* ----------------------------------------------------------------------- */
980 gentranf(c,nz,d,nn) := sum(c[i]*s**i,i,0,nz)/sum(d[i]*s**i,i,0,nn)$
982 /* ----------------------------------------------------------------------- */
983 /* impedance_chain(z1,z2,...n) - transfer function of an impedance chain */
984 /* ----------------------------------------------------------------------- */
985 impedance_chain(z1,z2,[zi]) :=
987 [_z1:[],_num:1,_sol,_vars,_n,_k,_ue,_i,ratprint:false,_eqs,
988 _z:flatten(append([z1,z2],zi))],
989 /* Repetition of the chain at odd number of parameters */
990 if oddp(length(_z)) then block(
993 for _j:1 thru _num do _z1:append(_z,_z1),
996 /* Building and solving the mesh-equations */
997 _eqs:makelist(_z[2*(_k-1)]*_i[_k-1]=_z[2*_k]*_i[_k]+_z[2*_k-1]*sum(_i[_j],_j,_k,_n),_k,2,_n),
998 _eqs:cons(_ue=_z[1]*sum(_i[_j],_j,1,_n)+_z[2]*_i[1],_eqs),
999 _vars:makelist(_i[_k],_k,1,_n),
1000 _sol:linsolve(_eqs,_vars), /* linsolve works better than solve (why?) */
1001 /* Calculation of the transfer function as the ratio Ua/Ue */
1002 ratsimp(subst(_sol,_z[2*_n]*_i[_n]/ _ue))
1005 /* ----------------------------------------------------------------------- */
1006 /* nilt(f,s,t) - inverse Laplace transform of f with numerically */
1007 /* calculated poles */
1008 /* ----------------------------------------------------------------------- */
1009 oilt(f,[st]):= /* Old version - just for test purposes */
1010 block([polyfactor:true,ratprint:false,ft,res,dt,fl,lvar,tvar,dummy],
1011 if length(st)=2 then [lvar,tvar]:st else [lvar,tvar]:['s,'t],
1012 if not listp(f) then f:[f],
1013 res:map(lambda([ff],
1014 [fl,dt]: if op(ff)="+" then _COMA_strip(args(ff)) else _COMA_strip(ff),
1015 ft:map(lambda([v],(if unit_step_included then unit_step(t) else 1)*
1017 then block([r:ilt(v,lvar,tvar)],
1018 if member("ilt",map(string,operators(r))) /* cannot be transformed analytically */
1019 then ev(expand(ilt(float(num(v)/allroots(float(denom(v)))),lvar,tvar)),numer)
1021 else ev(expand(ilt(float(num(v)/allroots(float(denom(v)))),lvar,tvar)),numer)
1023 ft:map(lambda([u,v],ev(u,t=t+v)),ft,log(dt)/s),
1024 /* apply("+",ft)),expandwrt(f,s)), 15.2.2019 */
1026 if length(res)=1 then res[1] else res
1028 nilt(f,[st]):= /* New version - 18.2019 */
1029 block([polyfactor:true,ratprint:false,ft,res,dt,fl,lvar,tvar,dummy],
1030 if length(st)=2 then [lvar,tvar]:st else [lvar,tvar]:['s,'t],
1031 if not listp(f) then f:[f],
1032 res:map(lambda([ff],
1033 [fl,dt]: _COMA_sdt(ff),
1034 ft:map(lambda([v],(if unit_step_included then unit_step(t) else 1)*
1036 then block([r:ilt(v,lvar,tvar)],
1037 if member("ilt",map(string,operators(r))) /* cannot be transformed analytically */
1038 then ev(expand(ilt(float(num(v)/allroots(float(denom(v)))),lvar,tvar)),numer)
1040 else ev(expand(ilt(float(num(v)/allroots(float(denom(v)))),lvar,tvar)),numer)
1042 ft:map(lambda([u,v],ev(u,t=t+v)),ft,log(dt)/s),
1043 /* apply("+",ft)),expandwrt(f,s)), 15.2.2019 */
1045 if length(res)=1 then res[1] else res
1047 try_symbolic_ilt:true$
1048 unit_step_included:true$
1050 /* ----------------------------------------------------------------------- */
1051 /* closed_loop(Fo) - calculates the closed loop transfer function Fw */
1052 /* ----------------------------------------------------------------------- */
1053 closed_loop(f,[x]):=
1055 [res,ratprint:false,hf,ht,n:5],
1056 if length(x)>0 then n:first(x),
1057 if not listp(f) then f:[f],
1058 res:map(lambda([ff],
1059 [hf,ht]:_COMA_strip(ff),
1060 if ht=[1] then (if not mapatom(ff/(1+ff)) then map(expand,xthru(ff/(1+ff))) else ff/(1+ff))
1061 else sum((-1)**((_k)+1)*first(hf)**(_k)*first(ht)**(_k),(_k),1,n)
1063 if length(res)=1 then res[1] else res
1066 /* ----------------------------------------------------------------------- */
1067 /* open_loop(Fw) - calculates the open loop transfer function Fo */
1068 /* ----------------------------------------------------------------------- */
1071 [res,ratprint:false],
1072 if not listp(f) then f:[f],
1073 res:map(lambda([ff],map(expand,xthru(ff/(1-ff)))), f),
1074 if length(res)=1 then res[1] else res
1077 /* ----------------------------------------------------------------------- */
1078 /* time_delay(tt,n,k) - Pade-approximation of order n (numerator order k) */
1079 /* ----------------------------------------------------------------------- */
1080 time_delay(tt,n,[k]):=
1082 else block([ratprint:false,numer:false,keepfloat:false,h],
1083 h:if emptyp(k) then n-1 else first(k),
1084 lambda([u],num(u)/denom(u))(first(pade(taylor(exp(-s*tt),s,0,h+n),h,n)))
1087 /* ----------------------------------------------------------------------- */
1088 /* ntfp(f) - checks whether f has only numeric coefficients */
1089 /* ----------------------------------------------------------------------- */
1091 block([ratprint:false,f1,zz,nn,res],
1092 if not listp(f) then f:[f],
1093 res:map(lambda([ff],
1095 zz:coefficient_list(num(f1),s),
1096 nn:coefficient_list(denom(f1),s),
1097 if is(setify(map(numberp,zz))={true}) and
1098 is(setify(map(numberp,nn))={true})
1099 then true else false
1101 if length(res)=1 then res[1] else res
1104 /* ----------------------------------------------------------------------- */
1105 /* transfer_function - transfer function from state space or equations */
1106 /* ----------------------------------------------------------------------- */
1107 transfer_function(a,[d]) :=
1109 [ratprint:false,A,B,C,D,sp:systemp(a)],
1111 if length(a)=4 then [A,B,C,D]:a,
1113 [A,B,C,D]:endcons(zeromatrix(length(a[3]),length(a[2][1])),a)),
1114 if sp and matrixp(D) and length(D)=1 and length(D[1])=1 then D:D[1][1],
1115 if sp then return(map(expand,xthru(C.invert(s*ident(length(A))-A).B+D))),
1116 if matrixp(a) then block(
1117 if length(d)=3 then [A,B,C,D]:[a,d[1],d[2],d[3]]
1118 else [A,B,C,D]:[a,d[1],d[2],zeromatrix(length(d[2]),length(d[1][1]))],
1119 if matrixp(D) and length(D)=1 and length(D[1])=1 then D:D[1][1],
1120 map(expand,ratsimp(C.invert(s*ident(length(A))-A).B+D)))
1122 m:coefmatrix(subst(linsolve(a,d[1]),flatten([d[3]])),flatten([d[2]])),
1123 if not listp(d[2]) and not listp(d[3]) then m[1][1] else m
1126 /* ----------------------------------------------------------------------- */
1127 /* sum_form - One of the 4 standard forms of a transfer function */
1128 /* (making one of the leading or absolute coefficients to 1) */
1129 /* ----------------------------------------------------------------------- */
1131 block([_zz,_nn,_zli,_nli,_sz,_kz,_kn,_n,_zli1,_nli1,_res,dt,_h],
1132 _n: if emptyp(nli) then 4 else nli[1],
1133 [f,dt]:_COMA_strip(f),
1134 _res:map(lambda([ff],
1135 _sz:if member(s,listofvars(ff)) then s
1136 elseif member(z,listofvars(ff)) then z else s,
1137 [_zz,_nn]:lambda([u],[num(u),denom(u)])(xthru(ff)),
1138 _zli:coefficient_list(_zz,_sz),
1139 _nli:coefficient_list(_nn,_sz),
1140 _zli1:delete(0,_zli),
1141 _nli1:delete(0,_nli),
1142 _kz:[last(_zli1),first(_zli1),last(_nli1),first(_nli1),last(_zli1),first(_zli1)][_n],
1143 _kn:[last(_zli1),first(_zli1),last(_nli1),first(_nli1),last(_nli1),first(_nli1)][_n],
1144 [_zli,_nli]:[_zli,_nli]/ [_kz,_kn],
1145 fullermap(lambda([u],if numberp(u) and equal(fix(u),u) then fix(u) else u),
1146 [1,1,1,1,_kz/ _kn,_kz/ _kn][_n]*apply("+",float(_zli)*makelist(_sz**i,i,0,length(_zli)-1))/
1147 apply("+",float(_nli)*makelist(_sz**i,i,0,length(_nli)-1)))),f)*dt,
1148 if length(_res)=1 then _res[1] else _res
1151 /* ----------------------------------------------------------------------- */
1152 /* product_form - Factorized forms of a transfer function (2017-06-08) */
1153 /* ----------------------------------------------------------------------- */
1154 product_form(f,[n]):=
1156 [f,dt]:_COMA_strip(f),
1157 res:map(lambda([ff],
1158 if ntfp(ff) and not mapatom(ff)
1159 then block([inflag:true,polyfactor:true,ratprint:false,fff,zz,nn,zli,nli,zdiv,ndiv],
1160 fff:xthru(float(ff)),
1161 fff:chop(allroots(num(fff))/allroots(denom(fff))),
1164 if not emptyp(n) and n[1]=1 then return(fullmap(lambda([u],if numberp(u)
1165 and abs(round(u)-u)<1e-10 then round(u) else u),(zz/nn))), /* high coefficient is 1 */
1166 zli:if not mapatom(zz) and op(zz)="*" then args(zz) else [zz],
1167 nli:if not mapatom(nn) and op(nn)="*" then args(nn) else [nn],
1168 zli:map(lambda([u],coefficient_list(u,s)),zli),
1169 nli:map(lambda([u],coefficient_list(u,s)),nli),
1170 zdiv:map(lambda([u],first(delete(0,u))),zli),
1171 ndiv:map(lambda([u],first(delete(0,u))),nli),
1172 fullmap(lambda([u],if numberp(u) and abs('round(u)-u) < 1.0e-10 and round(u)>0 then 'round(u) else u),
1174 apply("*",map(lambda([u],u.makelist(s^k,k,0,length(u)-1)),zli/zdiv)))/
1176 apply("*",map(lambda([u],u.makelist(s^k,k,0,length(u)-1)),nli/ndiv)))
1178 if length(res)=1 then res[1] else res)$
1180 /* ----------------------------------------------------------------------- */
1181 /* npartfrac - Partial fraction expansion with numeric coefficients */
1182 /* ----------------------------------------------------------------------- */
1184 block([polyfactor:true,ratprint:false],
1185 map(lambda([u],sum_form(u,3)),partfrac(num(G)/allroots(denom(G)),
1186 first(listofvars(G))))
1189 /* ======================================================================= */
1190 /* Optimization, Controller Design */
1191 /* ======================================================================= */
1193 /* ----------------------------------------------------------------------- */
1194 /* ise(f) - integral of squared error */
1195 /* ----------------------------------------------------------------------- */
1198 [n,sol,res,ratprint:false,dn,varlist,dt],
1199 [f,dt]:_COMA_strip(f),
1200 if setify(dt)#{1} then disp(_COMA_msg[3]),
1201 res:map(lambda([ff],
1203 block( /* Trick: solve darf gleichnamige Symbole in f */
1204 [a,b,aa,bb,c,d,i], /* nicht evaluieren! (macht es aber sonst :-) */
1205 [c,d]:[num(ff),denom(ff)],
1207 dn:coeff(expand(d),s,n),
1208 aa:sum('a[i]*s**i,i,0,n-1),
1209 bb:sum('b[i]*s**i,i,0,n-1),
1210 eq:coefficient_list(expand(c*subst(s=-s,c)-aa*subst(s=-s,d)-bb*d),s),
1211 varlist:flatten([makelist('a[i],i,0,n-1),makelist('b[i],i,0,n-1)])),
1212 sol:solve(eq,varlist),
1213 at('a[n-1]/dn,first(sol)))
1215 if length(res)=1 then res[1] else res
1218 /* ----------------------------------------------------------------------- */
1219 /* gain_optimum(fs,fr) - controller design according to the gain optimum */
1220 /* ----------------------------------------------------------------------- */
1221 gain_optimum(fs,fr,[v]):=
1223 [fw,omega,vars,eq,pli,li,ratprint:false,assume_pos:true,res],
1224 fw:ratsimp(closed_loop(fr*fs)),
1225 if emptyp(v) then vars:delete(s,listofvars(fr)) else vars:flatten([v]),
1226 res:map(lambda([ff],
1228 ff:ratsimp(ev(ff,s=%i*omega)),
1229 eq:cabs(num(ff))**2-cabs(denom(ff))**2,
1230 li:coefficient_list(expand(eq),omega),
1231 li:sublist(li,lambda([u],not atom(u))),
1232 li:sublist(li,lambda([u],is(op(u)="+"))),
1233 li:sublist(li,lambda([v],not apply("and",map(lambda([u],freeof(u,v)),vars)))),
1234 li:makelist(li[k],k,1,min(length(li),length(vars))),
1236 pli:sublist(li,lambda([x],
1237 (not member(false,map(lambda([u],is(rhs(u)>0)),x))))),
1238 if emptyp(pli) then li else first(pli))
1240 if length(res)=1 then res[1] else res
1242 /* ======================================================================= */
1243 /* Digital Algorithms */
1244 /* ======================================================================= */
1246 /* ----------------------------------------------------------------------- */
1247 /* stepf(f,[tt]) - step function of f with the optional sampling time tt */
1248 /* ----------------------------------------------------------------------- */
1249 stepf(f,[tt]):= /*11.1.2019 */
1250 (block([ratprint:false,T:1,index:1],
1251 if listp(tt) and length(tt)>0 then T:tt[1],
1254 if listp(f[1]) then (
1257 while t>f[index][1] and index<length(f) do index:index+1,
1258 if t>f[index][1] then f[index][2] else f[max(index-1,1)][2]))
1259 else f[max(1,min(floor(t/T)+1,length(f)))]
1261 else subst(t=T*floor(t/T),f)))$
1263 /* ----------------------------------------------------------------------- */
1264 /* algorithmp(f) - checks whether f is a digital algorithm */
1265 /* ----------------------------------------------------------------------- */
1266 algorithmp(f):=if listp(f) and not mapatom(f[1]) and
1267 op(f[1])="=" and lhs(f[1])='(x[k])
1268 then true else false$
1270 /* ----------------------------------------------------------------------- */
1271 /* eulerb(f) - Euler algorithm (with backward difference equations) */
1272 /* ----------------------------------------------------------------------- */
1274 block([ratprint:false,_Fz,_T,_zz,zz,nn,zz1,nn1,h,x,u,k,n],
1275 _T: if length(xx)>0 then first(xx) else T,
1276 _Fz:ratsimp(ev(F,s=(1/ _zz-1)/ _T*_zz)),
1277 zz:coefficient_list(num(_Fz),_zz),
1278 nn:coefficient_list(denom(_Fz),_zz),
1279 h:max(1,float(lmax(numbers(zz)))),
1280 zz:float(expand(zz/h)),
1281 nn:float(expand(nn/h)),
1282 zz1:apply("+",makelist(u[k-n],n,0,length(zz)-1)*zz),
1283 nn1:apply("+",makelist(x[k-n],n,0,length(nn)-1)*nn),
1284 h:coefficient_list(zz1-nn1,x[k]),
1285 [x[k]=-h[1]/h[2],_T]
1288 /* ----------------------------------------------------------------------- */
1289 /* eulerf(f) - Euler algorithm (with forward difference equations) */
1290 /* ----------------------------------------------------------------------- */
1292 block([ratprint:false,_Fz,_T,_zz,zz,nn,zz1,nn1,h,x,u,k,n],
1293 _T: if length(xx)>0 then first(xx) else T,
1294 _Fz:ratsimp(ev(F,s=(1/ _zz-1)/ _T)),
1295 zz:coefficient_list(num(_Fz),_zz),
1296 nn:coefficient_list(denom(_Fz),_zz),
1297 h:max(1,float(lmax(numbers(zz)))),
1298 zz:float(expand(zz/h)),
1299 nn:float(expand(nn/h)),
1300 zz1:apply("+",makelist(u[k-n],n,0,length(zz)-1)*zz),
1301 nn1:apply("+",makelist(x[k-n],n,0,length(nn)-1)*nn),
1302 h:coefficient_list(zz1-nn1,x[k]),
1303 [x[k]=-h[1]/h[2],_T]
1306 /* ----------------------------------------------------------------------- */
1307 /* tustin(f) - Tustin algorithm */
1308 /* ----------------------------------------------------------------------- */
1310 block([ratprint:false,_Fz,_T,_zz,zz,nn,zz1,nn1,h,x,u,k,n],
1311 _T: if length(xx)>0 then first(xx) else T,
1312 _Fz:ratsimp(ev(F,s=2*(1/ _zz-1)/ _T/(1/ _zz+1))),
1313 zz:coefficient_list(num(_Fz),_zz),
1314 nn:coefficient_list(denom(_Fz),_zz),
1315 h:max(1,float(lmax(numbers(zz)))),
1316 zz:float(expand(zz/h)),
1317 nn:float(expand(nn/h)),
1318 zz1:apply("+",makelist(u[k-n],n,0,length(zz)-1)*zz),
1319 nn1:apply("+",makelist(x[k-n],n,0,length(nn)-1)*nn),
1320 h:coefficient_list(zz1-nn1,x[k]),
1321 [x[k]=-h[1]/h[2],_T]
1324 /* ----------------------------------------------------------------------- */
1325 /* algorithm_step_response - Test function for an algorithm */
1326 /* ----------------------------------------------------------------------- */
1327 algorithm_step_response(f,[nn]):=
1328 block([varli,alg:rhs(f[1]),n:100,x,u],
1330 if length(nn)>0 then n:nn[1],
1331 varli:sublist(ev(listofvars(alg),k=0),lambda([v],not atom(v) and mapatom(v)
1332 and (op(v)=x or op(v)=u))),
1333 for k:lmin(flatten(map(args,varli))) thru -1 do (u[k]:0,x[k]:0), /* Init. */
1334 for k:0 thru n do (u[k]:1,x[k]:ev(alg)),
1335 makelist(float([k*f[2],x[k]]),k,0,n)
1339 /* ======================================================================= */
1341 /* ======================================================================= */
1343 /* ----------------------------------------------------------------------- */
1344 /* systemp(x) - checks whether x is a linear system with state matrices */
1345 /* ----------------------------------------------------------------------- */
1348 if not listp(x) then return(false),
1349 if length(x)<3 or length(x)>4 then return(false),
1350 if not matrixp(x[1]) then return(false),
1351 if not matrixp(x[2]) then return(false),
1352 if not matrixp(x[3]) then return(false),
1353 [nx,nu,ny]:[length(x[1]),length(x[2][1]),length(x[3])],
1354 if length(x[1][1]) # nx then return(false),
1355 if length(x[2]) # nx then return(false),
1356 if length(x[3][1]) # nx then return(false),
1357 if length(x)=3 then return(true),
1358 D: if not matrixp(x[4]) then matrix([x[4]]) else x[4],
1359 if length(D) # ny then return(false),
1360 if length(D[1]) # nu then return(false),
1364 /* ----------------------------------------------------------------------- */
1365 /* nsystemp(x) - checks whether x is a linear system with numeric coeffs. */
1366 /* ----------------------------------------------------------------------- */
1368 if systemp(x) and freeof(false,fullmap(numberp,x)) then true else false$
1370 /* ----------------------------------------------------------------------- */
1371 /* controller_canonical_form */
1372 /* ----------------------------------------------------------------------- */
1373 controller_canonical_form(f) :=
1375 [ratprint:false,zz,nn,zli,nli,A,B,C,D,o],
1376 [zz,nn]:[num(xthru(f)),denom(xthru(f))],
1377 zli:coefficient_list(zz,s), nli:coefficient_list(nn,s),
1378 zli:zli/last(nli), nli:nli/last(nli),
1380 zli:append(zli,makelist(0,k,1,length(nli)-length(zli))),
1381 A:genmatrix(lambda([u,v],if u=o then -nli[v] else if v-u=1 then 1 else 0),o,o),
1382 B:apply(matrix,endcons([1],makelist([0],k,1,o-1))),
1383 C:matrix(rest(map(lambda([u,v],u-last(zli)*v),zli,nli),-1)),
1388 /* ----------------------------------------------------------------------- */
1389 /* observer_canonical_form */
1390 /* ----------------------------------------------------------------------- */
1391 observer_canonical_form(f) :=
1393 [ratprint:false,zz,nn,zli,nli,A,B,C,D,o],
1394 [zz,nn]:[num(xthru(f)),denom(xthru(f))],
1395 zli:coefficient_list(zz,s), nli:coefficient_list(nn,s),
1396 zli:zli/last(nli), nli:nli/last(nli),
1398 zli:append(zli,makelist(0,k,1,length(nli)-length(zli))),
1399 A:genmatrix(lambda([u,v],if v=o then -nli[u] else if u-v=1 then 1 else 0),o,o),
1400 B:apply(matrix,rest(map(lambda([u,v],[u-last(zli)*v]),zli,nli),-1)),
1401 C:matrix(endcons(1,makelist(0,k,1,o-1))),
1406 /* ----------------------------------------------------------------------- */
1407 /* controllability_matrix */
1408 /* ----------------------------------------------------------------------- */
1409 controllability_matrix(x,[y]) :=
1411 [A,B]:if systemp(x) then [x[1],x[2]] else [x,first(y)],
1412 transpose(apply(matrix,makelist(
1413 flatten(args(transpose(A^^n.B))),n,0,length(A)-1))));
1415 /* ----------------------------------------------------------------------- */
1416 /* observability_matrix */
1417 /* ----------------------------------------------------------------------- */
1418 observability_matrix(x,[y]) :=
1420 [A,C]:if systemp(x) then [x[1],x[3]] else [x,first(y)],
1421 apply(matrix,makelist(flatten(args(C.A^^n)),n,0,length(A)-1)));
1423 /* ======================================================================= */
1424 /* Data Acquisition and Processing */
1425 /* ======================================================================= */
1427 /* ----------------------------------------------------------------------- */
1428 /* moving_average(x,n) - Moving Average of n elements in a list x */
1429 /* ----------------------------------------------------------------------- */
1430 moving_average(x,n):=
1432 h1:append(makelist(first(x),k,n/2),x,makelist(last(x),k,n/2)),
1433 for i:1 thru length(x) do
1435 h:makelist(h1[k],k,i,i+n-1),
1436 h2:endcons(float(apply("+",h)/n),h2)
1441 /* ----------------------------------------------------------------------- */
1442 /* regan - regression analysis (Wilhelm Haager 8.11.2014) */
1443 /* ----------------------------------------------------------------------- */
1444 /* f ... list of functions */
1445 /* p ... list of points */
1446 /* ----------------------------------------------------------------------- */
1448 lambda([x],f.(transpose(x).x)^^(-1).transpose(x).matrix(map(second,p)))
1449 (apply(matrix,map(lambda([u],ev(f,'x=u)),map(first,p))))$
1451 /* ----------------------------------------------------------------------- */
1452 /* add_noise(f,sd) - add a noise with standard deviation sd to f */
1453 /* ----------------------------------------------------------------------- */
1454 add_noise(f,[opts]):=
1455 block([sd,res,resu,zli,nli],
1457 nrandom(x):=float(x*cos(random(1.0)*2*%pi)*sqrt(-2*log(random(1.0)))),
1458 sli(n):=makelist(s**k,k,0,n),
1459 sd:if length(opts)>0 then opts[1] else 0.1,
1460 if not listp(f) then f:[f],
1461 res:map(lambda([ff],
1462 if numberp(ff) or mapatom(ff) then resu:ff+nrandom(sd)
1465 zli:map(lambda([v],v+nrandom(sd)),coefficient_list(num(ff))),
1466 nli:map(lambda([v],v+nrandom(sd)),coefficient_list(denom(ff))),
1467 resu:apply("+",zli*sli(length(zli)-1))/apply("+",nli*sli(length(nli)-1))
1470 if length(res)=1 then res[1] else res
1473 /* ----------------------------------------------------------------------- */
1474 /* mult_noise(f,sd) - multiply a noise with standard deviation sd to f */
1475 /* ----------------------------------------------------------------------- */
1476 mult_noise(f,[opts]):=
1477 block([sd,res,resu,zli,nli],
1479 nrandom(x):=float(x*cos(random(1.0)*2*%pi)*sqrt(-2*log(random(1.0)))),
1480 sli(n):=makelist(s**k,k,0,n),
1481 sd:if length(opts)>0 then opts[1] else 0.1,
1482 if not listp(f) then f:[f],
1483 res:map(lambda([ff],
1484 if numberp(ff) or mapatom(ff) then resu:ff*(1+nrandom(sd))
1487 zli:map(lambda([v],v*(1+nrandom(sd))),coefficient_list(num(ff))),
1488 nli:map(lambda([v],v*(1+nrandom(sd))),coefficient_list(denom(ff))),
1489 resu:apply("+",zli*sli(length(zli)-1))/apply("+",nli*sli(length(nli)-1))
1492 if length(res)=1 then res[1] else res
1495 /* ======================================================================= */
1496 /* Various Useful Functions */
1497 /* ======================================================================= */
1499 /* ----------------------------------------------------------------------- */
1500 /* sigma(t) - unit step function */
1501 /* ----------------------------------------------------------------------- */
1502 /* sigma(t):=if t < 0 then 0 else 1$ */
1504 /* ----------------------------------------------------------------------- */
1505 /* coefficient_list(p,s) - calculates a list of the coefficients of */
1506 /* the polynomial p(s) in ascending order */
1507 /* ----------------------------------------------------------------------- */
1508 /*coefficient_list(p,s) := lambda([v],map(lambda([u],coeff(v,s,u)), */
1509 /* makelist(i,i,0,hipow(v,s))))(expand(p))$ */
1510 coefficient_list(p,[x]) :=
1511 block([var,varlist:listofvars(p)],
1512 if length(x)>0 then var:first(x)
1513 else if length(varlist)=1 then var:first(varlist)
1514 else if member('s,varlist) then var:s
1515 else if member('z,varlist) then var:z,
1516 lambda([v],map(lambda([u],coeff(v,var,u)),
1517 makelist(i,i,0,hipow(v,var))))(expand(p))
1520 /* ----------------------------------------------------------------------- */
1521 /* get_option(o,l) - returns the option o from the list l (if existing) */
1522 /* option_exists(o,l) - tests, whether the option o exists in list l */
1523 /* list_option_exists(o,l) - tests, whether o exists and is a list */
1524 /* delete_option(o,l) - returns a list with option o deleted from l */
1525 /* set_option(o=v,l) - sets an option o with the value v */
1526 /* ----------------------------------------------------------------------- */
1527 get_option(o,l,[d]) := assoc(o,sublist(l,lambda([v],
1528 not atom(v) and op(v)="=")),if d=[] then false else first(d))$
1529 delete_option(o,l) ::= buildq([o,l],l:if option_exists(o,l) then
1530 sublist(l,lambda([u], atom(u) or op(u)#"=" or first(u)#o)) else l)$
1531 option_exists(o,l) := if member(o,map(first,sublist(l,lambda([v],
1532 not atom(v) and op(v)="=")))) then true$
1533 list_option_exists(o,l) :=
1534 if option_exists(o,l) then if listp(assoc(o,l)) then true$
1535 set_option(o,l) ::= buildq([o,l],l:endcons(o,delete_option(first(o),l)))$
1538 block([epsilon:1.0e-10],
1540 test(u):=if numberp(u) and cabs(u)<epsilon then 0 else u,
1541 if mapatom(f) then test(f) else fullmap(test,f)
1544 /* ----------------------------------------------------------------------- */
1545 /* // - parallel connection of resistances (WH, 26.9.2014) */
1546 /* ----------------------------------------------------------------------- */
1547 "//"([x]):=xthru(1/apply("+",1/x));
1550 /* ----------------------------------------------------------------------- */
1551 /* /_ - cis operator and cf operator for AC calculation (WH, 10.1.2016) */
1552 /* ----------------------------------------------------------------------- */
1553 "/_"(a,b):=rectform(float(a*(cos(b*%pi/180)+%i*sin(b*%pi/180))))$
1554 infix("/_",150,150)$
1555 cisform(_z)::=buildq(
1556 [_a:ev(cabs(_z),numer,eval),_b:ev(carg(_z)*180/%pi,numer,eval)],
1560 [_a:ev(cabs(_z),numer,eval),_b:ev(carg(_z)*180/%pi,numer,eval)],
1564 /* ----------------------------------------------------------------------- */
1565 /* spartfrac - partial fraction expansion with numeric and symbolic poles */
1566 /* ----------------------------------------------------------------------- */
1567 /* STILL UNDER DEVELOPMENT !
1568 spartfrac(G,[var]):=
1569 block([ratprint:false,variable,varli:listofvars(G)],
1570 if length(var)>0 then variable:var[1]
1571 else if member(s,varli) then variable:s
1572 else if member(z,varli) then variable:z
1573 else variable:first(varli),
1574 map(lambda([u],sum_form(u,3)),
1575 partfrac(num(G)/sallroots(denom(G)),variable))
1579 /* ----------------------------------------------------------------------- */
1580 /* sallroots - like allroots, but can contain symbolic factors and roots */
1581 /* ----------------------------------------------------------------------- */
1582 /* STILL UNDER DEVELOPMENT !
1583 sallroots(p,[var]):=
1584 block([polyfactor:true,p1,pli,pli1,pli2,variable,varli:listofvars(G)],
1585 if length(var)>0 then variable:var[1]
1586 else if member(s,varli) then variable:s
1587 else if member(z,varli) then variable:z
1588 else variable:first(varli),
1593 pli1:sublist(pli,lambda([u],freeof(variable,u) or length(listofvars(u))>1)),
1594 pli2:sublist(pli,lambda([u],not freeof(variable,u) and not length(listofvars(u))>1)),
1595 apply("*",cons(allroots(apply("*",pli2)),pli1)))
1600 /* ----------------------------------------------------------------------- */
1601 /* silt(f) - inverse Laplace transform with numeric and symbolic poles */
1602 /* ----------------------------------------------------------------------- */
1603 /* STILL UNDER DEVELOPMENT !
1605 block([polyfactor:true,ratprint:false,ft,res],
1606 if not listp(f) then f:[f],
1607 res:map(lambda([ff],
1609 if string(op(ft))="ilt" then
1611 ft:ilt(float(num(ff)/sallroots(denom(ff))),s,t),
1612 ft:ev(ft,float,expand)
1616 if length(res)=1 then res[1] else res
1620 /* ----------------------------------------------------------------------- */
1621 /* operators - list of all operators in an expr. (Wilhelm Haager 2013) */
1622 /* ----------------------------------------------------------------------- */
1628 else (if not member(op(x),opli)
1629 then opli:cons(op(x),opli),map(ops,args(x))),ops(x),opli);
1630 contains(op,x):=if member(op,operators(x)) then true;
1631 contains_any(op,x):=
1632 if member(true,map(lambda([u],contains(u,x)),flatten([op])))
1634 contains_all(op,x):=
1635 if member(false,map(lambda([u],contains(u,x)),flatten([op])))
1639 /* ----------------------------------------------------------------------- */
1640 /* numbers - list of all numbers in an expr. (Wilhelm Haager 2019) */
1641 /* ----------------------------------------------------------------------- */
1642 numbers(x):=block([li:[]],
1643 fullmap(lambda([u],if numberp(u) then li:cons(u,li),u),x),li)$
1645 /* ----------------------------------------------------------------------- */
1646 /* fullermap(f,x) - like fullmap, works also on atoms */
1647 /* ----------------------------------------------------------------------- */
1648 fullermap(f,x):=if mapatom(x) then f(x) else fullmap(f,x);
1650 /* ----------------------------------------------------------------------- */
1651 /* periodic(f,x1,x2) - makes the function f periodic (W.Haager 2019) */
1652 /* ----------------------------------------------------------------------- */
1654 block([par,x1,x2,ratprint:false],
1655 if length(x)>=2 then (x1:x[1],x2:x[2])
1656 else if length(x)=1 then (x1:0,x2:x[1])
1658 par:first(listofvars(f)),
1659 subst(par=par-(x2-x1)*(floor((par-x1)/(x2-x1))),f)
1664 /* ======================================================================= */
1665 /* Auxiliary Functions (only for internal use) */
1666 /* ======================================================================= */
1668 /* ----------------------------------------------------------------------- */
1669 /* _COMA_bpr(f) - calculation of an appropriate plot range */
1670 /* ----------------------------------------------------------------------- */
1673 [frequencies,omin,omax,g,dt],
1674 [f,dt]:_COMA_strip(f),
1675 f:map(lambda([u],if not mapatom(u) and op(u)='asymptotic then first(args(u)) else u),f),
1676 g:gain_crossover(f),
1677 frequencies:flatten([_COMA_cf(sublist(f,lambda([u],not _COMA_go(u)))),
1678 if length(g)>0 then map(last,flatten([g])) else []]),
1679 if length(frequencies)=0 then frequencies:[1],
1680 omin:apply(min,frequencies)/3, /* ADAPT RANGE HERE */
1681 omax:apply(max,frequencies)*3, /* ADAPT RANGE HERE */
1682 [10**floor(log(omin)/log(10)), 10**ceiling(log(omax)/log(10))]
1685 /* ----------------------------------------------------------------------- */
1686 /* _COMA_srat - calculates an appropriate display time for step_response */
1687 /* ----------------------------------------------------------------------- */
1691 [f,dt]:_COMA_strip(f),
1692 pl:flatten(poles(f)),
1693 pl:delete(0.0,pl), pl:delete(0,pl),
1694 if length(pl)=0 then 1 else
1696 l:map(lambda([u],float(5/sqrt(realpart(u)**2+imagpart(u)**2))),pl),
1697 apply(max,l))+lmax(-log(dt)/s)
1700 /* ----------------------------------------------------------------------- */
1701 /* _COMA_npv - next proper value for a plot range */
1702 /* ----------------------------------------------------------------------- */
1706 while f>last(li) do li:append(li,rest(li,length(li)-n)*10),
1707 li:append(li,rest(li,length(li)-n)*10), /* zur Vermeidung eines numer. Bugs */
1708 while f<first(li) do li:append(rest(li,n-length(li))/10,li),
1709 first(sublist(li,lambda([u],is(f<u))))
1712 /* ----------------------------------------------------------------------- */
1713 /* _COMA_cf(f) - cutoff frequencies */
1714 /* ----------------------------------------------------------------------- */
1717 if not listp(f) then f:[f],
1718 f:sublist(f, lambda([u],not _COMA_go(u))),
1719 pl:flatten([poles(f),zeros(f)]),
1720 pl:sort(map(lambda([u],sqrt(realpart(u)**2+imagpart(u)**2)),pl)),
1721 pl:sublist(pl,lambda([u],u>0))
1724 /* ----------------------------------------------------------------------- */
1725 /* _COMA_mx - "map extended", like map, adapts all lists according */
1726 /* to the last one, then performs a map over all lists */
1727 /* ----------------------------------------------------------------------- */
1728 _COMA_mx([lists]) :=
1731 for i:1 step 1 thru length(lists)-1 do
1733 block([n1:length(lists[i]),n2:length(last(lists)),res:[],j],
1734 for j:1 step 1 thru n2 do res:endcons(lists[i][mod(j-1,n1)+1],res),
1736 res:endcons(last(lists),res),
1740 /* ----------------------------------------------------------------------- */
1741 /* _COMA_strip(f) - strips off time delays from transfer functions. */
1742 /* _COMA_sdt(f) - newr, better version (2019-02-18) */
1743 /* ----------------------------------------------------------------------- */
1744 /* Result is a list with 2 elements: */
1745 /* 1. the transfer functions without time delay */
1746 /* 2. the according time delays */
1747 /* ----------------------------------------------------------------------- */
1749 block([h,d,fli:[],dli:[]],
1750 f:if listp(f) then f else[f],
1754 h:if not atom(e) then scanmap(lambda([u],if not atom(u) and op(u)="^" and first(args(u))=%e
1755 then (d:u, 1) else u),e)
1758 dli:endcons(d,dli)),
1761 _COMA_sdt(f):= /* strip off time delays */
1762 block([df,dfli,element,fli:[],dli:[],ratprint:false],
1764 dfli:if mapatom(df) or op(df)#"+" then [df] else args(df),
1765 fli:subst(%e=1,dfli),
1766 dli:ratsimp(dfli/fli),
1770 /* ----------------------------------------------------------------------- */
1771 /* _COMA_go(f) - checks whether f is a graphic object */
1772 /* ----------------------------------------------------------------------- */
1774 if (not atom(f) and member(op(f),['explicit,'points,'implicit,'parametric,
1775 'region,'polar,'polygon,'rectangle,'ellipse,'label,'stepf]))
1776 then true else false$
1778 /* ----------------------------------------------------------------------- */
1779 /* _COMA_ppo(f,defs,opts) - process plot options */
1780 /* ----------------------------------------------------------------------- */
1781 /* f ... List of functions, objects (or transfer functions) */
1782 /* f = 0 ... only global options are processed */
1783 /* f = 1 ... options are processed, but no functions */
1784 /* defs ... default options for a routine */
1785 /* opts ... explicitly given options for a routine */
1786 /* ----------------------------------------------------------------------- */
1787 _COMA_ppo(f,defs,opts):=
1789 [x,hli,fli,ar,preamble:[],list_options, list_exceptions:['dimensions,
1790 'xrange,'yrange,'trange,'user_preamble,'ip_grid,'ip_grid_in,'allocation]],
1791 if listp(coma_defaults)=false then coma_defaults:['grid=true],
1792 hli:append(coma_defaults,defs,opts),
1793 /* Gnuplot Praeambeln in Liste zusammenfassen */
1794 set_option('user_preamble=flatten(map(lambda([u],rhs(u)),
1795 sublist(hli,lambda([u],lhs(u)='user_preamble)))),hli),
1796 preamble:assoc(user_preamble,hli,[]),
1797 /* Mehrfache Optionen loeschen */
1798 for i:length(hli) step -1 thru 1 do hli:set_option(hli[i],hli),
1799 /* wx-Entscheidung */
1800 if option_exists('wx,hli) then
1801 block(if wxx#true and wxx#false then wxx:assoc('wx,hli),
1802 delete_option('wx,hli)),
1803 if option_exists('terminal,opts) then wxx:false,
1804 if option_exists('dimensions,opts) then wxplot_size:assoc('dimensions,hli,[]),
1805 preamble:assoc(user_preamble,hli,[]),
1806 /* Aspect Ratio in die Gnuplot Praeambel einbauen */
1807 if option_exists('aspect_ratio,hli) then block(
1808 ar:float(assoc('aspect_ratio,hli)),
1809 delete_option('aspect_ratio,hli),
1810 preamble:endcons(concat("set size ratio ",ar),flatten([preamble]))),
1811 if length(preamble)>0 then set_option(user_preamble=preamble,hli),
1812 /* Listenoptionen verarbeiten */
1813 list_options:sublist(hli,lambda([u],listp(rhs(u)))),
1814 for i:1 thru length(list_exceptions) do
1815 delete_option(list_exceptions[i],list_options),
1816 for i:1 thru length(list_options) do
1817 delete_option(lhs(list_options[i]),hli),
1818 if f=0 then return(hli),
1819 if f=1 then return(append(hli,list_options)),
1820 list_options:map(lambda([v],map(lambda([u],lhs(v)=u),rhs(v))),list_options),
1821 append(hli,apply(_COMA_mx,append(list_options,[f])))
1824 /* ======================================================================= */
1826 /* ======================================================================= */