File: __init__.py

package info (click to toggle)
python-casacore 3.7.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,488 kB
  • sloc: python: 4,170; cpp: 1,549; makefile: 67
file content (407 lines) | stat: -rwxr-xr-x 14,632 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# __init__.py: Top level .py file for python ftting interface
# Copyright (C) 2006,2007
# Associated Universities, Inc. Washington DC, USA.
#
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 3 of the License, or (at your
# option) any later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
# License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 675 Massachusetts Ave, Cambridge, MA 02139, USA.
#
# Correspondence concerning AIPS++ should be addressed as follows:
#        Internet email: aips2-request@nrao.edu.
#        Postal address: AIPS++ Project Office
#                        National Radio Astronomy Observatory
#                        520 Edgemont Road
#                        Charlottesville, VA 22903-2475 USA
#
# $Id: __init__.py,v 1.1 2006/10/20 06:30:03 mmarquar Exp $

"""Python interface to the Casacore scimath fitting module.

Introduction
============

The fitting module provides least squares fitting. It can handle linear and
non-linear; real and complex (including cases where unknowns are each other's
conjugate); complete and singular-value-decomposition; with or without
external constraints; general or specific cases.

------------------
The fitting module
------------------

For most uses we will create a single fitting object to work with::

    from casacore.fitting import fitserver
    dfit = fitserver()


More fitting tools can be created by either the fitter constructor (which
creates and returns a separate fitting tool), or by the fitter method of
an existing fitting tool, which returns a fit identifier, which can be
used to indicate a specific sub-fitter in the fitter used by including a
parameter 'id=' in all calls to the fitting tool's functions. The latter is
especially useful in the case where many simultaneous solutions are necessary:
it is more resource efficient, and also allows you to have an array of fit
indices to loop over. In both cases the parameters of the tool can be given
in the constructor (fitter method), or in a separate init method (see next
example of the highest level use)::

    from casacore.fitting import fitserver
    myfit = fitserver()		# general fitting object created
    				# (needs initializing before it can be used)
    cpid = myfit.fitter(ftype='complex') # and another (sub-)fitter
                                         # with an id

The theory behind the fitting module's operation is described in detail in
(`Note 224 <../../casacore/doc/notes/224.html>`_).


Fitting requires a model describing the data obtained. The model is a described
as a functional with parameters to be solved for. Functionals can be
pre-programmed functionals like poly, gauss1d, or free form like compiled. In
the latter case an expression string describes the model.

The model can depend on zero, one or more arguments, called x. The number of
arguments determines the dimension of the model.

Fitting also needs a set of data, called y. If the model is not 0-dimensional,
each value x will have an observed value. E.g. for each hour of the day x you
can have a measured temperature. Or in the case of multi-dimensions e.g. each
pair of hour of the day at each height above the surface (x0, x1) you should
have a y value.

--------
Examples
--------

Simple linear example
---------------------

The example uses a set of x coordinates::

    from numpy import arange
    x = -1 + 0.1*arange(21)

The 'observed' values used are a simple 1-dim polynomial of order 2:

1 + 2(x+1) + 0.03(x+1)^2 == 3.03 +2.06x + 0.03x^2

We fill these values using the polynomial functional::

    y = functionals.poly(2, [3.03, 2.06, 0.03])(x)

To take the average of these points, we can do::

    dfit.linear(functionals.compiled('p'), [], y)

Note that an expression uses p (which is p0), p1 and x, x0, x1. Note also that
since no argument is used in the expression, no x-values have to be given.

We can get solutions and errors from these data (see for details the separate
routine descriptions)::

    >>> dfit.solution()
    3.041
    # Compare with the result:
    >>> print sum(y)/len(y)
    3.041
    >>> print dfit.sd()	# standard deviation per observation
    1.27824
    >>> print dfit.stddev()	# standard deviation per weight
    1.27824
    >>> print dfit.error()	# errors in solved parameters
    array( [0.278934])
    >>> print dfit.rank()	# rank of solution
    1
    >>> print dfit.covariance() # covariance matrix
    [[0.047619]]

We can also try to use a 0-order polynomial. Note that a polynomial, even a
zero-order one, is a 1-dim function, and we need an x defined::

    >>> dfit.linear(dfs.poly(0), [], y)
    RuntimeError: Linear fitter x and y lengths disagree
    >>> dfit.linear(dfs.poly(0),x,y)
    print >>> dfit.solution()
    3.041

We would like to check the results, so we will do an average in a separate
fitter::

    >>> id = dfit.fitter()	# get a new fitter
    >>> dfit.linear(dfs.compiled('p'), [], y, fid=id) # get average
    >>> dfit.solution() - dfit.solution(fid=id) # check difference
    -4.44089e-16
    # to really show we recalculate and check separately:
    >>> dfit.linear(dfs.compiled('p'), [], array(y)/2, fid=id) # calculate new average
    >>> print dfit.solution() - dfit.solution(fid=id)
    [1 .5205]

A 1-order polynomial is now easy::

    >>> dfit.linear(dfs.poly(1), x, y)
    >>> print dfit.solution()
    [3.041, 2.06]
    >>> print dfit.chi2()
    0.00201894
    >>> print dfit.error()
    [0.00224944, 0.00371484]
    >>> print dfit.sd()
    0.0103082

Note that each 'equation' can also be given a weight or standard deviation.

2-dimensional example
---------------------

A 2-dim model is done the same way. The x vector has now n pairs of values.
The Glish rbind can help in creating these pairs::

    >>> x1 = arange(1, 6)
    >>> x2 = 0.1*x1
    >>> x1 = ravel(array([x1,x2]).transpose()) # combine into pairs. Check:
    >>> print x1
    array([ 1. ,  0.1,  2. ,  0.2,  3. ,  0.3,  4. ,  0.4,  5. ,  0.5])
    >>> dfit.linear(dfs.compiled('p*x + p1*sin(x1)'), x1,
    dfs.compiled('3*x+7*sin(x[2])').f(x1))
    >>> print dfit.solution()
    [ 3. 7.]


Non-linear simple example
-------------------------

If the model is non-linear in the parameters to be solved, the functional
method should be used. The main difference is that a guess solution must be
inserted in the model parameters. In the following that is not necessary,
since the default zero values suffice if the function is linear::

    >>> dfit.functional(dfs.compiled('p*x + p1*sin(x1)'), x1,
        dfs.compiled('3*x+7*sin(x[2])').f(x1), id=id)
    >>> dfit.solution(fid=id)
    [ 3. 7.]
    >>> dfit.solution(fid=id)-dfit.solution()
    [  6.17284002e-13  -6.35846931e-12]
    # Try with an intial guess
    >>> dfit.functional(dfs.compiled('p*x + p1*sin(x1)', [3,7]), x1,
    dfs.compiled('3*x+7*sin(x[2])').f(x1), fid=id2)
    >>> dfit.solution(fid=id2)
    [ 3. 7.]
    >>> dfit.solution(fid=id2)-dfit.solution()
    [  6.17284002e-13  -6.35846931e-12]


Functional variety
------------------

Just to show the model can be anything, we redo the fit of an order 1
polynomial to the x, y data::

    >>> dfit.linear(dfs.poly(1), x,y)
    >>> dfit.solution()
    [ 3.041 2.06]

Now try the same by a sum of odd and even polynomials of default order
(note the order)::

    a = dfs.compound()
    a.add(dfs.functional('oddp'))
    a.add(dfs.functional('evenp'))
    dfit.linear(a,x,y,id=id2)
    dfit.solution(id=id2)
    [ 2.06 3.041]

And the combination of an odd (2x) and an even polynomial (3)::

    a = dfs.combi()
    a.add(dfs.functional('oddp', params=2))
    a.add(dfs.functional('evenp', params=3))
    dfit.linear(a, x, y)
    >>> dfit.solution()
    [ 1.03 1.01367]


Use constraints
---------------

We have measured a number of anlgles around a triangle. Each angle is measured
10 times (nominally 50, 60, 70 deg). Solving the angles will give::

    >>> import numpy
    >>> yz = numpy.array([numpy.zeros(10) + 50 + numpy.random.normal(0,1,10),
    numpy.zeros(10) + 60 + numpy.random.normal(0,1,10),
    numpy.zeros(10) + 70 + numpy.random.normal(0,1,10)]).flatten()
    # Create 3*10 equations
    >>> xz = array([1,0,0]*10 + [0,1,0]*10 + [0,0,1]*10)
    # The equation used and solve
    >>> f = dfs.compiled('p*x+p1*x1+p2*x2')
    >>> dfit.linear(f, xz, yz)
    >>> print dfit.solution(), 'sum=', sum(dfit.solution())
    [49.7079 60.2427 70.092]  sum= 180.043
    >>> dfit.error()
    [ 0.334828 0.334828 0.334828]
    # Add a constraint: sum of angles 180deg
    >>> dfit.addconstraint(x=[1,1,1],y=180)
    >>> dfit.linear(f,xz,yz)
    >>> print dfit.solution(), 'sum=', sum(dfit.solution())
    [ 49.6937 60.2285 70.0778]  sum= 180
    >>> print dfit.error()
    [ 0.273413 0.273413 0.273413]
    # Add another constraint, since we know second angle 60deg
    >>> dfit.addconstraint(x=[0,1,0], y=60)
    >>> dfit.linear(f,xz,yz)
    >>> print dfit.solution(), 'sum=', sum(dfit.solution())
    [ 49.8079 60 70.1921]  sum= 180
    >>> print dfit.error()
    [0.239827 0 0.239827]


Non-linear equation and constraints
-----------------------------------

In the following we have 2 Gaussian profiles and an offset. We add some noise,
and solve assuming we have a fair estimate of the position of the Gaussians.
Note that if the first estimate is beyond the real half-value point, the
fitting will be difficult, due to the derivatives changing sign::

    # The profile to generate and the parameters to use
    # (in essence 10 + 20 * exp (-((x-10)/4)^2) + 10 * exp(-((x-33)/4)^2) )
    f = dfs.compiled('p6+p0*exp(-((x-p1)/p2)^2) + p3*exp(-((x-p4)/p5)^2)',
    [20, 10, 4, 10, 33, 4, 10])
    xg = 0.5 * numpy.arange(1, 101) - 0.5
    yg = numpy.array(f(xg)) + numpy.random.normal(0,0.3,100)
    # Make an intial guess
    f.set_parameters([22, 11, 5, 10, 30, 5, 9])
    # Solve
    dfit.clearconstraints()
    dfit.functional(f,xg,yg)
    print dfit.solution()
    print dfit.solution() - numpy.array([20., 10, 4, 10, 33, 4, 10])
    print dfit.error()
    [0.211312 0.0334257 0.0527771 0.213666 0.0652003 0.102782 0.082641]
    # We know that the two lines have a peak ratio of 2: Amp1-2Amp2 = 0
    dfit.addconstraint([1, 0, 0, -2, 0, 0, 0])
    dfit.functional(f, xg, yg)
    print dfit.solution()
    print dfit.solution() - numpy.array([20., 10, 4, 10, 33, 4, 10])
    print dfit.solution()[0]/dfit.solution()[3]
    print dfit.error()
    # We know that the lines originated in same place: width1 == width2
    # Note that the default assumed value is 0.0
    dfit.addconstraint([0, 0, 1, 0, 0, -1, 0])
    dfit.functional(f, xg, yg)
    print dfit.solution()
    dfit.solution() - numpy.array([20, 10, 4, 10, 33, 4, 10])
    dfit.solution()[2]-dfit.solution()[5]
    dfit.error()
    # And see what happens if we assume that the widths are 4
    dfit.addconstraint([0, 0, 1, 0, 0, 0, 0], 4)
    dfit.functional(f, xg, yg)
    dfit.solution()
    dfit.solution() - [20, 10, 4, 10, 33, 4, 10]
    dfit.error()



Deficient solutions and SVD constraints
---------------------------------------

*DOES NOT WORK*

In some cases solutions of the least-squares equations is not completely
possible. An example is e.g. the solution of the closures equations in
synthesis calibrations, where a missing phase zero and slope and a missing
absolute gain cannot be solved for. The fitting described here will always
provide a solution, even in the case of a set of incomplete equations. After
the solution the deficiency can be checked. If there is a rank deficiency,
the set of 'constraints' that makes a solution possible (in a way similar to
SVD, i.e. providing a missing set of orthogonal equations) is available
through the constr function::

    # Provide a set of equations.
    x = array([1,1,1]*10)
    y = 180 + numpy.zeros(10) + numpy.random.normal(0, 3, 10)
    f = dfs.functional('hyper', 3)
    dfit.linear(f,x,y)
    dfit.deficiency()
    2
    dfit.solution()
    [60.0262 60.0262 60.0262]
    dfit.constraint()
    [-1 0 1 -1 1 0]
    # The SVD constraints can be used as constraints in subsequent solutions:
    dfit.addconstraint(x=dfit.constraint(1))
    T
    dfit.addconstraint(x=dfit.constraint(2))
    T
    dfit.linear(f,xz,yz)
    T
    dfit.solution()
    [60.0262 60.0262 60.0262]
    dfit.rank()
    3
    dfit.deficiency()
    0
    dfit.error()
    [0.202801 0.202801 0.202801]


Complex fitting
---------------

The fitter can handle functions of complex variables. In the following example
a second order polynomial is first fitted real with a first order linear
polynomial. The same is repeated complex (with real data); and then a complex
value is fitted. An example of a 2-dimensional non-linear function is also
given::

    # Define x and y data
    >>> x = -1 + numpy.arange(0,21)*0.1
    >>> y = dfs.poly(2, [3.03, 2.06, 0.03])(x)

    # fit a first order polynomial
    >>> dfit.linear(dfs.poly(1), x,y)
    >>> print 'linear', dfit.solution()
    linear [ 3.041 2.06]

    # Get a complex fitter and see the same fit
    >>> id1 = dfit.fitter()
    >>> dfit.set(ftype='complex', fid=id1)
    >>> dfit.linear(dfs.poly(1, dtype='complex'), x, y, fid=id1)
    >>> dfit.solution(fid=id1)
    [ 3.041+0j 2.06+0j]

    # Make a complex yi and redo
    >>> yi = dfs.poly(2, [3.03, 2.06, 0.03])(x)
    >>> yi = yi - 3j*array(dfs.poly(2, [3.03+0j, 2.06, 0.03])(x))
    >>> dfit.linear(dfs.poly(1, dtype='complex'), x, yi, fid=id1)
    >>> dfit.solution(fid=id1)
    [ 3.041-9.123j 2.06-6.18j]

    # A non-linear 2-dimensional function, real and complex
    >>> id2 = dfit.fitter()
    >>> dfit.functional(dfs.compiled('p*x + p1*sin(x1)', [3,7]), x1,
                    dfs.compiled('3*x+7*sin(x[2])')(x1), fid=id2)
    >>> x1 = arange(1,6)
    >>> x2 = 0.1*x1
    >>> x1 = array(zip(x1,x2)).flatten()
    >>> dfit.functional(dfs.compiled('p*x + p1*sin(x1)', [3,7]),x1,
                    dfs.compiled('3*x+7*sin(x[2])').f(x1), fid=id2)
    >>> dfit.solution(fid=id2)
    >>> dfit.set(type=dfit.complex(), fid=id2)
    >>> dfit.functional(dfs.compiled('p*x + p1*sin(x1)', [3,7]),x1,
                    dfs.compiled('3*x+7*sin(x[2])').f(x1), fid=id2)
    >>> dfit.solution(fid=id2)

"""
from .fitting import *