I have a code with global variables that are used by functions defined in the header. I would like to separate the functions and main by putting them in two different files. The problem is that when trying to pass these variables from main to one of the functions, it gives me problems.
The original code is this:
# -*- coding: utf-8 -*-
# == METHOD 2 ==
# Basic usage of optimize.leastsq
from scipy import optimize as opt
import numpy as np
import f02_graphs as grp
#method_2 = "leastsq"
def calc_R(xc, yc):
""" Calculate the distance of each 2D points from the center (xc, yc) """
return np.sqrt((x-xc)**2 + (y-yc)**2)
def f_2(c):
""" Calculate the algebraic distance between the 2D points and the mean circle centered at c=(xc, yc) """
Ri = calc_R(*c)
return Ri - Ri.mean()
# Datos.
x = [36, 36, 19, 18, 33, 26]
y = [14, 10, 28, 31, 18, 26]
basename = 'arc'
# Coordenadas del baricentro.
x_m = np.mean(x)
y_m = np.mean(y)
barycenter = x_m, y_m
# Aplicamos un algoritmo para aproximar el centro de la circunferencia.
circle_center, ier = opt.leastsq(f_2, barycenter)
xc, yc = circle_center
# Calculamos la distancia al centro de cada par x,y.
R_xy = calc_R(xc, yc)
# Calculamos el promedio de todoas las distancias de R_xy.
R_2 = R_xy.mean()
My version where I have separated the code into two files is as follows:
main
from scipy import optimize as opt
import numpy as np
import f02_graphs as grp
import f08_adjustments as adj
x = [36, 36, 19, 18, 33, 26]
y = [14, 10, 28, 31, 18, 26]
basename = 'arc'
# Coordenadas del baricentro.
x_m = np.mean(x)
y_m = np.mean(y)
barycenter = x_m, y_m
# Aplicamos un algoritmo para aproximar el centro de la circunferencia.
circle_center, ier = opt.leastsq(adj.f_2, barycenter)
xc, yc = circle_center
# Calculamos la distancia al centro de cada par x,y.
R_xy = adj.calc_R(x, y, xc, yc)
# Calculamos el promedio de todoas las distancias de R_xy.
R_2 = R_xy.mean()
functions
import numpy as np
def calc_R(x, y, xc, yc):
radius = np.sqrt((x - xc)**2 + (y - yc)**2)
return radius
def f_2(c):
Ri = calc_R(*c)
dist = Ri - Ri.mean()
return dist
I understand that the function f_2
has to pass the values x, y
as a parameter, but I don't know how to do it because it is actually opt.leastsq
the one that calls f_2
.
How can I solve this problem?
Initial approach, explanation
The function's
leastsq
mission is to find a certain pair, let 's call(xc,yc)
it , that minimizes the value of the sum of "errors" between that point and another series of them, for which it needs two elements:(xc, yc)
(your code usesf_2
such a function)(xc, yc)
(and for this we passbarycenter
)The way it works
leastsq
is to call the function that you have passed as the first parameter (in our casef_2
) passing as a parameter the value of the point we are looking for, initiallybarycenter
. It waits for the functionf_2
to return a list of errors and with it calculates the sum of the squares and estimates in which direction the initial point should be moved to decrease that sum. It calls againf_2
with the new value of point and gets a new list of errors, and so on, until it thinks it has found the point where the (quadratic) sum of errors is least. That will be what returns as the first result, which your code saves incircle_center
.As you can see, the function
f_2
is called multiple times, but each time it is called, only one parameter is passed: the point that is currently being considered as a candidate. To calculate the list of errors, what the function does is:c
is the candidate point. The first thing it does with it is callcalc_R(*c)
, which "unpacks" the tuplec
into its two components, and passes these two components to the functioncalc_R()
, which therefore receives two parameters: thex
e -coordinatesy
of the candidate point.What it does
calc_R()
is calculate the distance from that "supposed center" to each of the points being considered, and returns a vector with those distances. The functionf_2
then subtracts the average of each of these distances and returns the list of results. That list will be the errors that itleastsq
will try to minimize, by moving the candidate to the center of the circumference in the appropriate direction.Now, we see that you
calc_R()
need to know, in addition to the "candidate", the rest of the points of the problem. Instead of receiving them as parameters, it takes them from global variables.The problem
We don't want that. We want to pass them as a parameter to
calc_R()
, but who is callingcalc_R()
isf_2()
, then it would bef_2()
the one who has to pass them. But for that itf_2()
would have to receive them as a parameter too and... who is callingf_2()
? Well, it turns out that it isleastsq
, so we need some way of "injecting" those parameters in the call thatleastsq
makesf_2()
, since we have already seen that by default it will only pass one parameter: the "centre candidate"We have two possible solutions:
args
ofleastsq
functools.partial()
Wearing
args
Through this parameter we can specify
leastsq
what additional parameters we want it to pass tof_2()
when it is invoked (in addition to the candidate center point, which it will pass to anyway). These extra parameters would be passed after the candidate point.So we would invoke
leastsq
as follows:In addition to this, of course, we have to rewrite it
f_2()
so that it actually picks up these parameters and passes them tocalc_R()
, like so:And finally it
calc_R()
would look like this:(Note that since
x
ey
are lists, a vector operation is actually being computed, which will return another list)Wearing
functools.partial()
This approach uses functional programming to build a new function from a given one.
Suppose we have this function that takes three parameters (note that in this case the first two are
x
ey
, instead of the last two):And suppose also that
leastsq
it did not have the parameterargs
, but that the function it calls will always be passed a single parameter (c
).In this case we would need a function that receives a single parameter (
c
) but that internally callsf_2
passing the three parameters (x
,y
,c
). This is whatfunctools.partial
allows us to build. The following expression:does just that. Create a function
f
that you could call like this:f(resto_de_parámetros)
and which will internally callf_2(x,y, resto_de_parámetros)
.Therefore, with this approach we would have:
Reviewing the documentation of
leastsq
you can see that it has a parameterargs
which has the following documentation:That is, any additional parameters of the function should be indicated by means of a tuple in this parameter. In your case it should be something like this: