Split class property in two properties - python

I solve a system of two equations in a class property, and it returns the solution -- values of two variables. I'd like both values be a properties of the class -- how do I achieve this without solving the system twice? Here's an example
#!/usr/bin/python3
class Test(object):
pass
def ab(self):
print("Calc!")
a = 1
b = 2
return [a,b]
#property
def a(self):
return self.ab()[0]
#property
def b(self):
return self.ab()[1]
test = Test()
print(test.a)
print(test.b)
It outputs:
Calc!
1
Calc!
2
so it actually "solved" the system of equations (ab property) twice. If it had solved it once, than the output would be:
Calc!
1
2
How do I achieve this?
Edit
Example with a system:
#!/usr/bin/python3
import scipy
from scipy.optimize import fsolve
class Test(object):
def __init__(self, c, d):
self.c = c
self.d = d
def ab(self):
print("Calc!")
result = fsolve(lambda x: [
x[0] + 2*x[1] + self.c
, 3*x[0] - x[1] + self.d
], [1,1])
return result
#property
def a(self):
return self.ab()[0]
#property
def b(self):
return self.ab()[1]
test = Test(-5,2)
print(test.a)
print(test.b)
Gives:
Calc!
0.142857142857
Calc!
2.42857142857
I want it to solve a system only once:
Calc!
0.142857142857
2.42857142857
Edit 2
Real code:
#!/usr/bin/env python3
import argparse, os, sys
# ==============
## parsing args:
parser = argparse.ArgumentParser()
argsLip = parser.add_argument_group('Properties of lipid:')
argsLip.add_argument('-A', '--area',
help = "incompressible area, Ų (default to %(default)s)",
dest = 'A_n',
action = 'store',
type = float,
default = 20.0,
)
argsLip.add_argument('-L',
help = "basic length in Å (default to %(default)s)",
dest = 'L',
action = 'store',
type = float,
default = 15.0,
)
argsLip.add_argument('-K', '--K-coef',
help = "bending rigidity in kTL (default to %(default)s)",
dest = 'K_f_coef',
action = 'store',
type = float,
default = 0.33,
)
argsMem = parser.add_argument_group('Properties of membrane:')
argsMem.add_argument('-g', '--gamma',
help = "surface tension, erg/cm² (default to %(default)s)",
dest = 'γ',
action = 'store',
type = float,
default = 30.0,
)
argsEnv = parser.add_argument_group('Properties of environment:')
argsEnv.add_argument('-T', '--temperature',
help = "temperature, K (default to %(default)s)",
dest = 'T',
action = 'store',
type = float,
default = 323.0,
)
argsCalc = parser.add_argument_group('Calc options:')
argsCalc.add_argument('-a', '--a-trial',
help = "trial value of a to be used in nsolve (default to %(default)s)",
dest = 'a_trial',
action = 'store',
type = float,
default = 2.0,
)
args = parser.parse_args()
# =========
## imports:
# symbolic:
import sympy
from sympy import symbols, solve, nsolve, pprint, diff, S, Function
from sympy import sqrt as Sqrt
sympy.init_printing(pretty_print=True, use_unicode=True, wrap_line=False, no_global=True)
# numeric:
import scipy
from scipy import sqrt
from scipy.optimize import fsolve
# constants:
from scipy import pi as π
from scipy.constants import k as k_SI
k = k_SI * 10**7 # J/K → erg/K
# =========
## program:
class MonoFlexible_symbolic(object):
"This class initiates common symbolic expressions to be used in all MonoFlexible classes."
def __init__(self):
a, l = symbols("a l", real=True, positive=True)
b = Function('b')(a, l)
ν = Function('ν')(l)
equation = (
3 / (4 * b)
+ 1 / ( 2 * Sqrt(2) * b**(S(3)/4) )
- ν * ( Sqrt(a) - 1 )**2
)
equation_diff_a = equation.diff(a)
equation_diff_a2 = equation_diff_a.diff(a)
equation_diff_l = equation.diff(l) .subs(ν.diff(l) , -3*ν)
equation_diff_l2 = equation_diff_l.diff(l) .subs(ν.diff(l,2), 12*ν)
equation_diff_al = equation_diff_a.diff(l) .subs(ν.diff(l) , -3*ν)
db_da = solve( equation_diff_a , b.diff(a) )[0]
d2b_da2 = solve( equation_diff_a2 , b.diff(a,2) )[0]
db_dl = solve( equation_diff_l , b.diff(l) )[0]
d2b_d2l = solve( equation_diff_l2 , b.diff(l,2) )[0]
d2b_dadl = solve( equation_diff_al , b.diff(a).diff(l) )[0]
# print("db_da =")
# pprint(
# db_da
# )
# print("d2b_da2 =")
# pprint("d2b_da2 =",
# d2b_da2
# )
# print("db_dl =")
# pprint(
# db_dl
# )
# print("d2b_dl2 =")
# pprint(
# d2b_d2l
# )
# print("d2b_dadl =")
# pprint(
# cancel(d2b_dadl[0])
# )
self.db_da_func = lambda aa, bb, νν: db_da.subs({
a: aa
, b: bb
, ν: νν
}).evalf()
self.d2b_da2_func = lambda aa, bb, νν: d2b_da2.subs({
a: aa
, b: bb
, ν: νν
}).evalf()
self.db_dl_func = lambda aa, bb, νν: db_dl.subs({
a: aa
, b: bb
, ν: νν
}).evalf()
self.d2b_d2l_func = lambda aa, bb, νν: d2b_dl2.subs({
a: aa
, b: bb
, ν: νν
}).evalf()
self.d2b_dadl_func = lambda aa, bb, νν: d2b_dadl.subs({
a: aa
, b: bb
, ν: νν
}).evalf()
class MonoFlexible(MonoFlexible_symbolic):
def __init__(self,
γ : "Surface tension of the membrane, erg/cm²",
T : "Temperature, K",
L : "Length of the hydrocarbon chain, Å",
A_n : "Non-compressible area of the lipid, Ų",
a_trial : "Initial value for fsolve, default to 2.0" = None,
K_f_coef : "K_f = k T L * K_f_coef, default to 1/3" = None,
) -> "Calculates thermodynamic properties of flexible string":
super().__init__()
self.__γ = γ
self.__T = T
self.__L = L
self.__A_n = A_n
self.__a_trial = a_trial
self.__K_f_coef = K_f_coef
#property
def A_n_Å2(self):
return self.__A_n
#property
def A_n(self):
return self.__A_n * 10**(-16) # Ų → cm²
#property
def L_Å(self):
return self.__L
#property
def L(self):
return self.__L * 10**(-8) # Å → cm
#property
def γ(self):
return self.__γ
#property
def T(self):
return self.__T
#property
def a_trial(self):
"Initial value for numerical equation solving function to find area per lipid."
a_trial = self.__a_trial or 2.0
return a_trial
#property
def K_f_coef(self):
K_f_coef = self.__K_f_coef or 1/3
return K_f_coef
#property
def K_f(self):
"Rigidity of the string."
return k * self.T * self.L * self.K_f_coef
#property
def ν(self):
return self.K_f * self.A_n / (
π * k * self.T * self.L**3
)
#property
def ab(self):
print("Calc!")
ab = fsolve( lambda x: [
3 / ( 4 * x[1] )
+ 1 / ( 2 * sqrt(2) * x[1]**(3/4) )
- self.ν * (sqrt(x[0]) - 1)**2
,
- k * self.T / self.A_n * self.db_da_func(x[0], x[1], self.ν) * self.ν * (sqrt(x[0]) - 1)**2
- self.γ
]
, [2., 300.] )
return ab
#property
def a(self):
return self.ab[0]
#property
def b(self):
return self.ab[1]
# ======
## calc:
def main():
flexible_kwargs = {
"γ" : args.γ,
"T" : args.T,
"L" : args.L,
"A_n" : args.A_n,
"a_trial" : args.a_trial,
"K_f_coef" : args.K_f_coef,
}
flexible = MonoFlexible(**flexible_kwargs)
print( "ν = {ν:.5f}".format(ν=flexible.ν) )
print( "a = {a:.2f}".format(a=flexible.a) )
print( "b = {b:.2f}".format(b=flexible.b) )
# python code run faster in a function:
if __name__ == "__main__":
main()
Works with default parameters, so in order to test it -- just run it.

Sounds to me like you're just trying to cache the solution. Here's a way to do that that involves creating another property:
class Test(object):
def __init__(self):
pass
#property
def solution(self):
try:
return self._solution
except AttributeError:
self._solution = self.ab()
return self._solution
def ab(self):
print("Calc!")
a = 1
b = 2
return [a,b]
#property
def a(self):
return self.solution[0]
#property
def b(self):
return self.solution[1]
test = Test()
print(test.a)
print(test.b)
Output:
Calc!
1
2
Update!
In Python 3.8 a built-in decorator to do this was added to the standard library named functools.cached_property() that makes implementing this scheme in an easier and more succinct way (plus it's thread-safe):
import functools
class Test(object):
def __init__(self):
pass
#functools.cached_property
def ab(self):
print("Calc!")
a = 1
b = 2
return [a,b]
#property
def a(self):
return self.ab[0]
#property
def b(self):
return self.ab[1]

assigning the properties as the results are created could be a way to go
#!/usr/bin/python3
class Test(object):
def __init__(self):
pass
def ab(self):
print("Calc!")
self._a = a = 1
self._b = b = 2
return [a,b]
#property
def a(self):
return self._a
#a.setter
def a(self, value):
self._a = value
#property
def b(self):
return self._b
#b.setter
def b(self, value):
self._b = value
test = Test()
results = test.ab()
print(test.a)
print(test.b)

Your current class doesn't have any statefulness at all, so it doesn't seem like an example of good class design. Its exact operation is completely hard-coded...it is hard to know what advantage you get from having it.
Perhaps what you want is something more along the lines of
class Test(object):
def __init__(self):
self.a, self.b = fsolve(lambda x: [
x[0] + 2*x[1] - 5
, 3*x[0] - x[1] + 2
], [1,1])

Related

Design pattern for relational and optional parameters?

I am to build a class that accepts a series of inputs via the constructor method, then perform a calculation with calculate() using these parameters. The trick here is that these parameters might be available sometimes and other times might not. There however, is a given equation between the variables, such that the missing ones can be calculated from the equations. Here is an example:
I know that:
a = b * c - d
c = e/f
I am to calculate always a+b+c+d+e+f
Here is what I have so far:
class Calculation:
def __init__(self, **kwargs):
for parameter, value in kwargs.items():
setattr(self, '_'.format(parameter), value)
#property
def a(self):
try:
return self._a
except AttributeError:
return self._b * self._c - self._d
#property
def b(self):
try:
return self._b
except AttributeError:
return (self._a + self._d) / self._c
... // same for all a,b,c,d,e,f
def calculate(self):
return sum(self.a+self.b+self.c+self.d+self.e+self.f)
then use as:
c = Calculation(e=4,f=6,b=7,d=2)
c.calculate()
however, some other time might have other variables like:
c = Calculation(b=5,c=6,d=7,e=3,f=6)
c.calculate()
My question is: What would be a good design pattern to use in my case? So far, it seems a bit redundant to make a #property for all variables. The problem it must solve is to accept any variables (minimum for which calculation is possible) and based on the equation I have, figure out the rest, needed for calculation.
This seems like a good candidate for the getattr function. You can store the keyword arguments directly in the class and use that dictionary to either return a known parameter as attribute or infer an unspecified value "on the fly" based on other formulas that you know of:
class Calculation:
def __init__(self, **kwargs):
self.params = kwargs
self.inferred = {
"a" : lambda: self.b * self.c - self.d,
"c" : lambda: self.e / self.f,
"result": lambda: self.a+self.b+self.c+self.d+self.e+self.f
}
def __getattr__(self, name):
if name in self.params:
return self.params[name]
if name in self.inferred:
value = self.inferred[name]()
self.params[name] = value
return value
r = Calculation(b=1,d=3,e=45,f=9).result
print(r) # 65.0 (c->45/9->5, a->1*5-3->2)
Note that, if you have very complicated calculations for some of the parameters, you can use functions of the class as the implementation of the lambdas in the self.inferred dictionary.
If you're going to use this pattern for many formulas, you might want to centralize the boilerplate code in a base class. This will reduce the work needed for new calculation classes to only having to implement the inferred() function.:
class SmartCalc:
def __init__(self, **kwargs):
self.params = kwargs
def __getattr__(self, name):
if name in self.params:
return self.params[name]
if name in self.inferred():
value = self.inferred()[name]()
self.params[name] = value
return value
class Calculation(SmartCalc):
def inferred(self):
return {
"a" : lambda: self.b * self.c - self.d,
"b" : lambda: (self.a+self.d)/self.c,
"c" : lambda: self.e / self.f,
"d" : lambda: self.c * self.b - self.a,
"e" : lambda: self.f * self.c,
"f" : lambda: self.e / self.c,
"result": lambda: self.a+self.b+self.c+self.d+self.e+self.f
}
With enough content in inferred(), you can even use this approach to obtain any value from a combination of the others:
valueF = Calculation(a=2,b=1,c=5,d=3,e=45,result=65).f
print(valueF) # 9.0
EDIT
If you want to make this even more sophisticated, you can improve getattr to allow specification of dependencies in the inferred() dictionary.
For example:
class SmartCalc:
def __init__(self, **kwargs):
self.params = kwargs
def __getattr__(self, name):
if name in self.params:
return self.params[name]
if name in self.inferred():
calc = self.inferred()[name]
if isinstance(calc,dict):
for names,subCalc in calc.items():
if isinstance(names,str): names = [names]
if all(name in self.params for name in names):
calc = subCalc; break
value = calc()
self.params[name] = value
return value
import math
class BodyMassIndex(SmartCalc):
def inferred(self):
return {
"heightM" : { "heightInches": lambda: self.heightInches * 0.0254,
("bmi","weightKg"): lambda: math.sqrt(self.weightKg/self.bmi),
("bmi","weightLb"): lambda: math.sqrt(self.weightKg/self.bmi)
},
"heightInches" : lambda: self.heightM / 0.0254,
"weightKg" : { "weightLb": lambda: self.weightLb / 2.20462,
("bmi","heightM"): lambda: self.heightM**2*self.bmi,
("bmi","heightInches"): lambda: self.heightM**2*self.bmi
},
"weightLb" : lambda: self.weightKg * 2.20462,
"bmi" : lambda: self.weightKg / (self.heightM**2)
}
bmi = BodyMassIndex(heightM=1.75,weightKg=130).bmi
print(bmi) # 42.44897959183673
height = BodyMassIndex(bmi=42.45,weightKg=130).heightInches
print(height) # 68.8968097135968 (1.75 Meters)
EDIT2
A similar class could be designed to process formulas expressed as text. This would allow a basic form of term solver using a newton-raphson iterative approximation (at least for 1 degree polynomial equations):
class SmartFormula:
def __init__(self, **kwargs):
self.params = kwargs
self.moreToSolve = True
self.precision = 0.000001
self.maxIterations = 10000
def __getattr__(self, name):
self.resolve()
if name in self.params: return self.params[name]
def resolve(self):
while self.moreToSolve:
self.moreToSolve = False
for formula in self.formulas():
param = formula.split("=",1)[0].strip()
if param in self.params: continue
if "?" in formula:
self.useNewtonRaphson(param)
continue
try:
exec(formula,globals(),self.params)
self.moreToSolve = True
except: pass
def useNewtonRaphson(self,name):
for formula in self.formulas():
source,calc = [s.strip() for s in formula.split("=",1)]
if name not in calc: continue
if source not in self.params: continue
simDict = self.params.copy()
target = self.params[source]
value = target
try:
for _ in range(self.maxIterations):
simDict[name] = value
exec(formula,globals(),simDict)
result = simDict[source]
resultDelta = target-result
value += value*resultDelta/result/2
if abs(resultDelta) < self.precision/2 :
self.params[name] = round(simDict[name]/self.precision)*self.precision
self.moreToSolve = True
return
except: continue
With this approach the BodyMassIndex calculator would be easier to read:
import math
class BodyMassIndex(SmartFormula):
def formulas(self):
return [
"heightM = heightInches * 0.0254",
"heightM = ?", # use Newton-Raphson solver.
"heightInches = ?",
"weightKg = weightLb / 2.20462",
"weightKg = heightM**2*bmi",
"weightLb = ?",
"bmi = weightKg / (heightM**2)"
]
This lets you obtain/use terms for which the calculation formula is not explicitly stated in the list (e.g. heightInches computed from heightM which is computed from bmi and weightKg):
height = BodyMassIndex(bmi=42.45,weightKg=130).heightInches
print(height) # 68.8968097135968 (1.75 Meters)
Note: The formulas are expressed as text and executed using eval() which may be much slower than the other solution. Also, the Newton-Raphson algorithm is OK for linear equations but has its limitations for curves that have a mix of positive and negative slopes. For example, I had to include the weightKg = heightM**2*bmi formula because obtaining weightKg based on bmi = weightKg/(heightM**2) needs to solve a y = 1/x^2 equation which Newton-Raphson can't seem to handle.
Here's an example using your original problem:
class OP(SmartFormula):
def formulas(self):
return [
"a = b * c - d",
"b = ?",
"c = e/f",
"d = ?",
"e = ?",
"f = ?",
"result = a+b+c+d+e+f"
]
r = OP(b=1,d=3,e=45,f=9).result
print(r) # 65.0
f = OP(a=2,c=5,d=3,e=45,result=65).f
print(f) # 9.0
class ABCD(SmartFormula):
def formulas(self) : return ["a=b+c*d","b=?","c=?","d=?"]
#property
def someProperty(self): return "Found it!"
abcd = ABCD(a=5,b=2,c=3)
print(abcd.d) # 1.0
print(abcd.someProperty) # Found it!
print(abcd.moreToSolve) # False
Just precompute the missing values in __init__ (and since you know what the 5 values are, be explicit rather than trying to compress the code using kwargs):
# Note: Make all 6 keyword-only arguments
def __init__(self, *, a=None, b=None, c=None, d=None, e=None, f=None):
if a is None:
a = b * c - d
if c is None:
c = e / f
self.sum = a + b + c + d + e + f
def calculate(self):
return self.sum
[New Answer to complement previous one]
I felt my answer was getting too big so I'm adding this improved solution in a separate one.
This is a basic algebra solver to for simple equations that will output an assignment statement for a different term of the input equation:
For example:
solveFor("d","a=b+c/d") # --> 'd=c/(a-b)'
With this function, you can further improve the SmartFormula class by attempting to use algebra before reverting to Newton-Raphson. This will provide more reliable results when the equation is simple enough for the solveFor() function.
The solveFor() function can solve the equation for any term that appears only once in the formula. It will "understand" the calculation as long as the components to solve are only related with basic operations (+, -, *, /, **). Any group in parentheses that does not contain the target term will be processed "as is" without being further interpreted. This allows you to place complex functions/operators in parentheses so that other terms can be solved even in the presence of these special calculations.
import re
from itertools import accumulate
def findGroups(expression):
levels = list(accumulate(int(c=="(")-int(c==")") for c in expression))
groups = "".join([c,"\n"][lv==0] for c,lv in zip(expression,levels)).split("\n")
groups = [ g+")" for g in groups if g ]
return sorted(groups,key=len,reverse=True)
functionMap = [("sin","asin"),("cos","acos"),("tan","atan"),("log10","10**"),("exp","log")]
functionMap += [ (b,a) for a,b in functionMap ]
def solveFor(term,equation):
equation = equation.replace(" ","").replace("**","†")
termIn = re.compile(f"(^|\\W){term}($|\\W)")
if len(termIn.findall(equation)) != 1: return None
left,right = equation.split("=",1)
if termIn.search(right): left,right = right,left
groups = { f"#{i}#":group for i,group in enumerate(findGroups(left)) }
for gid,group in groups.items(): left = left.replace(group,gid)
termGroup = next((gid for gid,group in groups.items() if termIn.search(group)),"##" )
def moveTerms(leftSide,rightSide,oper,invOper):
keepLeft = None
for i,x in enumerate(leftSide.split(oper)):
if termGroup in x or termIn.search(x):
keepLeft = x; continue
x = x or "0"
if any(op in x for op in "+-*/"): x = "("+x+")"
rightSide = invOper[i>0].replace("{r}",rightSide).replace("{x}",x)
return keepLeft, rightSide
def moveFunction(leftSide,rightSide,func,invFunc):
fn = leftSide.split("#",1)[0]
if fn.split(".")[-1] == func:
return leftSide[len(fn):],fn.replace(func,invFunc)
return leftSide,rightSide
left,right = moveTerms(left,right,"+",["{r}-{x}"]*2)
left,right = moveTerms(left,right,"-",["{x}-{r}","{r}+{x}"])
left,right = moveTerms(left,right,"*",["({r})/{x}"]*2)
left,right = moveTerms(left,right,"/",["{x}/({r})","({r})*{x}"])
left,right = moveTerms(left,right,"†",["log({r})/log({x})","({r})†(1/{x})"])
for func,invFunc in functionMap:
left,right = moveFunction(left,right,func,f"{invFunc}({right})")
for sqrFunc in ["math.sqrt","sqrt"]:
left,right = moveFunction(left,right,sqrFunc,f"({right})**2")
for gid,group in groups.items(): right = right.replace(gid,group)
if left == termGroup:
subEquation = groups[termGroup][1:-1]+"="+right
return solveFor(term,subEquation)
if left != term: return None
solution = f"{left}={right}".replace("†","**")
# expression clen-up
solution = re.sub(r"(?<!\w)(0\-)","-",solution)
solution = re.sub(r"1/\(1/(\w)\)",r"\g<1>",solution)
solution = re.sub(r"\(\(([^\(]*)\)\)",r"(\g<1>)",solution)
solution = re.sub(r"(?<!\w)\((\w*)\)",r"\g<1>",solution)
return solution
Example Uses:
solveFor("x","y=(a+b)*x-(math.sin(1.5)/322)") # 'x=(y+(math.sin(1.5)/322))/(a+b)'
solveFor("a","q=(a**2+b**2)*(c-d)**2") # 'a=(q/(c-d)**2-b**2)**(1/2)'
solveFor("a","c=(a**2+b**2)**(1/2)") # 'a=(c**2-b**2)**(1/2)'
solveFor("a","x=((a+b)*c-d)*(23+y)") # 'a=(x/(23+y)+d)/c-b'
sa = solveFor("a","y=-sin((x)-sqrt(a))") # 'a=(x-asin(-y))**2'
sx = solveFor("x",sa) # 'x=a**(1/2)+asin(-y)'
sy = solveFor("y",sx) # 'y=-sin(x-a**(1/2))'
Note that you can probably find much better algebra 'solvers' out there, this is just a simple/naive solution.
Here is an improved version of the SmartFormula class that uses solveFor() to attempt an algebra solution before reverting to Newton-Raphson approximations:
class SmartFormula:
def __init__(self, **kwargs):
self.params = kwargs
self.precision = 0.000001
self.maxIterations = 10000
self._formulas = [(f.split("=",1)[0].strip(),f) for f in self.formulas()]
terms = set(term for _,f in self._formulas for term in re.findall(r"\w+\(?",f) )
terms = [ term for term in terms if "(" not in term and not term.isdigit() ]
self._formulas += [ (term,f"{term}=solve('{term}')") for term in terms]
self(**kwargs)
def __getattr__(self, name):
if name in self.params: return self.params[name]
def __call__(self, **kwargs):
self.params = kwargs
self.moreToSolve = True
self.params["solve"] = lambda n: self.autoSolve(n)
self.resolve()
return self.params.get(self._formulas[0][0],None)
def resolve(self):
while self.moreToSolve:
self.moreToSolve = False
for param,formula in self._formulas:
if self.params.get(param,None) is not None: continue
try:
exec(formula,globals(),self.params)
if self.params.get(param,None) is not None:
self.moreToSolve = True
except: pass
def autoSolve(self, name):
for resolver in [self.algebra, self.newtonRaphson]:
for source,formula in self._formulas:
if self.params.get(source,None) is None:
continue
if not re.search(f"(^|\\W){name}($|\\W)",formula):
continue
resolver(name,source,formula)
if self.params.get(name,None) is not None:
return self.params[name]
def algebra(self, name, source, formula):
try: exec(solveFor(name,formula),globals(),self.params)
except: pass
def newtonRaphson(self, name, source,formula):
simDict = self.params.copy()
target = self.params[source]
value = target
for _ in range(self.maxIterations):
simDict[name] = value
try: exec(formula,globals(),simDict)
except: break
result = simDict[source]
resultDelta = target-result
if abs(resultDelta) < self.precision :
self.params[name] = round(value/self.precision/2)*self.precision*2
return
value += value*resultDelta/result/2
This allowed the example class (BodyMassIndex) to avoid specification of the "weightKg = heightM**2*bmi" calculation because the algebra solver can figure it out. The improved class also eliminates the need to indicate auto-solving term names ("term = ?").
import math
class BodyMassIndex(SmartFormula):
def formulas(self):
return [
"bmi = weightKg / (heightM**2)",
"heightM = heightInches * 0.0254",
"weightKg = weightLb / 2.20462"
]
bmi = BodyMassIndex()
print("bmi",bmi(heightM=1.75,weightKg=130)) # 42.44897959183673
print("weight",bmi.weightLb) # 286.6006 (130 Kg)
bmi(bmi=42.45,weightKg=130)
print("height",bmi.heightInches) # 68.8968097135968 (1.75 Meters)
For the original question, this is simple as can be:
class OP(SmartFormula):
def formulas(self):
return [
"result = a+b+c+d+e+f",
"a = b * c - d",
"c = e/f"
]
r = OP(b=1,d=3,e=45,f=9).result
print(r) # 65.0
f = OP(a=2,c=5,d=3,e=45,result=65).f
print(f) # 9.0
Newton-Raphson was not used in any of these calculations because the algebra solves them in priority before trying the approximations

Calculator Dependency Tree Python (sympy / numpy)

I would like to have users enter specific values and then the system computes numerous results based on what these - My program is getting very complicated with just a few functions. I have included an example with 3 simple functions and 6 variables with the following relationships:
The Code I have is as follows:
class MyCalculator:
def __init__(self):
self.a = None
self.b = None
self.c = None
self.d = None
self.e = None
self.f = None
def set(self, field, val):
if field == "a": self.a = val
if field == "b": self.b = val
if field == "c": self.c = val
if field == "d": self.d = val
if field == "e": self.e = val
for i in range(10): # circle round a few times to ensure everything has computed
if self.a and self.b:
self.c = self.a * self.b
if self.a and self.c:
self.b = self.c / self.a
if self.b and self.c:
self.a = self.c / self.b
if self.b and self.d:
self.e = self.b + self.d
if self.e and self.b:
self.d = self.e - self.b
if self.e and self.d:
self.b = self.e - self.d
if self.c and self.e:
self.f = self.c / self.e
if self.f and self.e:
self.e = self.f * self.e
if self.f and self.c:
self.e = self.c / self.f
def status(self):
print(f"a = {self.a} b = {self.b} c = {self.c} d = {self.d} e = {self.e} f = {self.f} ")
Then If i run the following code:
example1 = MyCalculator()
example1.set("a", 5)
example1.set("e", 7)
example1.set("c", 2)
example1.status()
This will print out a = 5.0 b = 0.40000000000000036 c = 2.0000000000000018 d = 6.6 e = 7.0 f = 0.285714285714286
I would like a much simpler way to achieve the same result using something like sympy and numpy but so far I cant find anything that will work
There's a live version of this solution online you can try for yourself
Here's a complete solution that uses Sympy. All you need to do is enter your desired expressions in the exprStr tuple at the top of the MyCalculator definition, and then all of the dependency satisfaction stuff should take care of itself:
from sympy import S, solveset, Symbol
from sympy.parsing.sympy_parser import parse_expr
class MyCalculator:
# sympy assumes all expressions are set equal to zero
exprStr = (
'a*b - c',
'b + d - e',
'c/e - f'
)
# parse the expression strings into actual expressions
expr = tuple(parse_expr(es) for es in exprStr)
# create a dictionary to lookup expressions based on the symbols they depend on
exprDep = {}
for e in expr:
for s in e.free_symbols:
exprDep.setdefault(s, set()).add(e)
# create a set of the used symbols for input validation
validSymb = set(exprDep.keys())
def __init__(self, usefloat=False):
"""usefloat: if set, store values as standard Python floats (instead of the Sympy numeric types)
"""
self.vals = {}
self.numify = float if usefloat else lambda x: x
def set(self, symb, val, _exclude=None):
# ensure that symb is a sympy Symbol object
if isinstance(symb, str): symb = Symbol(symb)
if symb not in self.validSymb:
raise ValueError("Invalid input symbol.\n"
"symb: %s, validSymb: %s" % (symb, self.validSymb))
# initialize the set of excluded expressions, if needed
if _exclude is None: _exclude = set()
# record the updated value of symb
self.vals[symb] = self.numify(val)
# loop over all of the expressions that depend on symb
for e in self.exprDep[symb]:
if e in _exclude:
# we've already calculated an update for e in an earlier recursion, skip it
continue
# mark that e should be skipped in future recursions
_exclude.add(e)
# determine the symbol and value of the next update (if any)
nextsymbval = self.calc(symb, e)
if nextsymbval is not None:
# there is another symbol to update, recursively call self.set
self.set(*nextsymbval, _exclude)
def calc(self, symb, e):
# find knowns and unknowns of the expression
known = [s for s in e.free_symbols if s in self.vals]
unknown = [s for s in e.free_symbols if s not in known]
if len(unknown) > 1:
# too many unknowns, can't do anything with this expression right now
return None
elif len(unknown) == 1:
# solve for the single unknown
nextsymb = unknown[0]
else:
# solve for the first known that isn't the symbol that was just changed
nextsymb = known[0] if known[0] != symb else known[1]
# do the actual solving
sol = solveset(e, nextsymb, domain=S.Reals)
# evaluate the solution given the known values, then return a tuple of (next-symbol, result)
return nextsymb, sol.subs(self.vals).args[0]
def __str__(self):
return ' '.join(sorted('{} = {}'.format(k,v) for k,v in self.vals.items()))
Testing it out:
mycalc = MyCalculator()
mycalc.set("a", 5)
mycalc.set("e", 7)
mycalc.set("c", 2)
print(mycalc)
Output:
a = 5 b = 2/5 c = 2 d = 33/5 e = 7 f = 2/7
One of the neat things about Sympy is that it uses rational math, which avoids any weird rounding errors in, for example, 2/7. If you'd prefer to get your results as standard Python float values, you can pass the usefloat flag to MyCalculator:
mycalc = MyCalculator(usefloat=True)
mycalc.set("a", 5)
mycalc.set("e", 7)
mycalc.set("c", 2)
print(mycalc)
Output:
a = 5.0 b = 0.4 c = 2.0 d = 6.6 e = 7.0 f = 0.2857142857142857
In [107]: a=2.
In [108]: a=5.
In [109]: b=0.4
In [110]: c=lambda: a*b
In [111]: d=6.6
In [112]: e=lambda: b+d
In [113]: f=lambda: c()/e()
In [114]: print(a,b,c(), d, e(), f())
5.0 0.4 2.0 6.6 7.0 0.2857142857142857
You can probably capture the above logic in a class.
It would be possible to hold 'variables' as _a, _b and _d. Then a(), b() and d() could be functions that return _a etc...
More a pointer than a whole answer but it may help.
Using a structure like that below it would be possible to create a situation where you always call a function and not need to know when to use a and c() but always use a() and c().
In [121]: def var(init=0.0):
...: def func(v=None):
...: nonlocal init
...: if v==None: return init
...: init=v
...: return func
...:
In [122]: a=var(100.)
In [123]: a()
Out[123]: 100.0
In [124]: a(25.)
In [125]: a()
Out[125]: 25.0

Tuple Class object takes no values

I create an instance of class Vector2 with line AB = Vector2.from_points(A, B)
But python errors out with TypeError: object() takes no parameters
on line AB = Vector2.from_points(A,B)
and on line return Vector2(cls, P2[0]-P1[0], P2[1]-P1[1])
so I figured maybe the book is wrong (I'm looking at examples in a book). I subtract the Vector2 and cls from the def from_points statement so that...
this is how the new line reads: return (P2[0]-P1[0], P2[1]-P1[1])
When I do this a receive the vector value from def from_points equal too (5, 10)
But then python errors out on:
print AB.get_magnitude()
with AttributeError: 'tuple' object has no attribute 'get_magnitude'
so without the code related to Vector2 and cls the program won't read AB as a class object but it seems that I'm not formatting it right so it won't go through.
I have been stuck on this for days.
#Vector Test
import math
class Vector2(object):
def _init_(self, x=0.0,y=0.0):
self.x = x
self.y = y
def _str_(self):
return"(%s,%s)"%(self.x,self.y)
#classmethod
def from_points(cls, P1, P2):
return Vector2(cls, P2[0]-P1[0],P2[1]-P1[1])
def get_magnitude(self):
return math.sqrt(self.x**2 + self.y**2)
A = (15.0, 20.0)
B = (20.0, 30.0)
AB = Vector2.from_points(A, B)
print AB
print AB.get_magnitude()
CHANGED CODE:
#Vector Test
import math
class Vector2(object):
def _init_(self, x=0.0,y=0.0):
self.x = x
self.y = y
def _str_(self):
return"(%s,%s)"%(self.x,self.y)
#classmethod
def from_points(cls, P1, P2):
return (P2[0]-P1[0],P2[1]-P1[1])
def get_magnitude(self):
return math.sqrt(self.x**2 + self.y**2)
A = (15.0, 20.0)
B = (20.0, 30.0)
AB = Vector2.from_points(A, B)
print AB
print AB.get_magnitude()
that's (probably) what you mean.
#Vector Test
import math
class Vector2(object):
def __init__(self, x=0.0,y=0.0):
self.x = x
self.y = y
def __str__(self):
return"(%s,%s)"%(self.x,self.y)
#classmethod
def from_points(cls, P1, P2):
return Vector2(P2.x-P1.x,P2.y-P1.y)
def get_magnitude(self):
return math.sqrt(self.x**2 + self.y**2)
A = Vector2(15.0, 20.0)
B = Vector2(20.0, 30.0)
AB = Vector2.from_points(A, B)
print( AB )
print( AB.get_magnitude() )

Best way to lazily update calculated properties by hashing dependent attributes

I'm trying to make a bunch of geometric objects which have their intrinsic geometric properties (center point, radius, lengths, etc.), as well as properties to help plot them (like x, y, z coordinates for a triangular mesh, arc resolution, etc.).
Since calculating the x, y, z coordinates is an expensive task for some of the shapes (like a triangular prism with edge rounding), I don't want to do it every time a property is changed, but only when the coordinates are requested. Even then though, it shouldn't be necessary to recalculate them if the shape's definition hasn't changed.
So my solution has been to create a "hash" which is is simply a tuple of all parameters which define the shape's "state." If the hash is unchanged, then the previously calculated coordinates can be re-used, otherwise, the coordinates must be recalculated. So I'm using the hash as a way to store the signature or fingerprint of the shape's definition.
I think what I have works, but I wonder if there are more robust ways to handle this that take advantage of __hash__ or id's or something. That feels like overkill to me, but I'm open to suggestions.
Here's my implementation for a sphere. I'm using Mayavi for plotting at the end, which you can skip/ignore if you don't have Mayavi.
#StdLib Imports
import os
#Numpy Imports
import numpy as np
from numpy import sin, cos, pi
class Sphere(object):
"""
Class for a sphere
"""
def __init__(self, c=None, r=None, n=None):
super(Sphere, self).__init__()
#Initial defaults
self._coordinates = None
self._c = np.array([0.0, 0.0, 0.0])
self._r = 1.0
self._n = 20
self._hash = []
#Assign Inputs
if c is not None:
self._c = c
if r is not None:
self._r = r
if n is not None:
self._n = n
#property
def c(self):
return self._c
#c.setter
def c(self, val):
self._c = val
#property
def r(self):
return self._r
#r.setter
def r(self, val):
self._r = val
#property
def n(self):
return self._n
#n.setter
def n(self, val):
self._n = val
#property
def coordinates(self):
self._lazy_update()
return self._coordinates
def _lazy_update(self):
new_hash = self._get_hash()
old_hash = self._hash
if new_hash != old_hash:
self._update_coordinates()
def _get_hash(self):
return tuple(map(tuple, [self._c, [self._r, self._n]]))
def _update_coordinates(self):
c, r, n = self._c, self._r, self._n
dphi, dtheta = pi / n, pi / n
[phi, theta] = np.mgrid[0:pi + dphi*1.0:dphi,
0:2*pi + dtheta*1.0:dtheta]
x = c[0] + r * cos(phi) * sin(theta)
y = c[1] + r * sin(phi) * sin(theta)
z = c[2] + r * cos(theta)
self._coordinates = x, y, z
self._hash = self._get_hash()
if __name__ == '__main__':
from mayavi import mlab
ns = [4, 6, 8, 10, 20, 50]
sphere = Sphere()
for i, n in enumerate(ns):
sphere.c = [i*2.2, 0.0, 0.0]
sphere.n = n
mlab.mesh(*sphere.coordinates, representation='wireframe')
mlab.show()
As suggested, here's a version that uses a dictionary to store the hash as a key:
#StdLib Imports
import os
#Numpy Imports
import numpy as np
from numpy import sin, cos, pi
class Sphere(object):
"""
Class for a sphere
"""
def __init__(self, c=None, r=None, n=None):
super(Sphere, self).__init__()
#Initial defaults
self._coordinates = {}
self._c = np.array([0.0, 0.0, 0.0])
self._r = 1.0
self._n = 20
#Assign Inputs
if c is not None:
self._c = c
if r is not None:
self._r = r
if n is not None:
self._n = n
#property
def c(self):
return self._c
#c.setter
def c(self, val):
self._c = val
#property
def r(self):
return self._r
#r.setter
def r(self, val):
self._r = val
#property
def n(self):
return self._n
#n.setter
def n(self, val):
self._n = val
#property
def _hash(self):
return tuple(map(tuple, [self._c, [self._r, self._n]]))
#property
def coordinates(self):
if self._hash not in self._coordinates:
self._update_coordinates()
return self._coordinates[self._hash]
def _update_coordinates(self):
c, r, n = self._c, self._r, self._n
dphi, dtheta = pi / n, pi / n
[phi, theta] = np.mgrid[0:pi + dphi*1.0:dphi,
0:2 * pi + dtheta*1.0:dtheta]
x = c[0] + r * cos(phi) * sin(theta)
y = c[1] + r * sin(phi) * sin(theta)
z = c[2] + r * cos(theta)
self._coordinates[self._hash] = x, y, z
if __name__ == '__main__':
from mayavi import mlab
ns = [4, 6, 8, 10, 20, 50]
sphere = Sphere()
for i, n in enumerate(ns):
sphere.c = [i*2.2, 0.0, 0.0]
sphere.n = n
mlab.mesh(*sphere.coordinates, representation='wireframe')
mlab.show()
Why not simply keep a flag:
class Sphere(object):
def __init__(self, ...):
...
self._update_coordinates()
...
#c.setter
def c(self, val):
self._changed = True
self._c = val
...
def _lazy_update(self):
if self._changed:
self._update_coordinates()
def _update_coordinates(self):
...
self._changed = False

How to deal with a lot variables access in different function/class in python

The code which I wanna improved now looks something like below,
which f0 and f1(or more than 2 function) need the same variables.
I have to code about 50 lines to describe the variable setting at each function.
how can I do this more pythontic?
--f0.py
import csv
def gen(csv_f):
# var define
for row in csv.DictReader(open(csv_f)):
c += row['x']
...
a = 1
b = 2
...
# do sth in f0
xx = a + b
...
str = ...
return str
--f1.py
import csv
def gen(csv_f):
# var define
for row in csv.DictReader(open(csv_f)):
c += row['x']
...
a = 1
b = 2
...
# do sth in f1
xx = a*b + b
...
str = ...
return str
--genstr.py
from f0 import *
from f1 import *
f = open('xxx.x','w')
f.write(f0.gen(1)+f1.gen(1))
f.close()
(I don't really know how to use class, but I found this could help my problem
just describe maybe it will help understanding my question )
I try to do it with class, so i can access by inherit conf.
I know I can access by 'self.a', but is there any way I can direct use 'a' in the function?
--conf.py
class conf:
def __init__(self, csv_f):
# var define
for row in csv.DictReader(open(csv_f)):
c += row['x']
...
self.a = 1
self.b = 2
...
--f0.py
import conf
class f0(conf):
def __init__(self):
config.__init__(self, csv_f) #this line is not correct
def gen():
# var set
c = self.c
a = self.a
b = self.b
# do sth in f0
xx = a + b
...
str = ...
return str
--f1.py
import conf
class f1(conf):
def __init__(self):
config.__init__(self, csv_f) #this line is not correct
def gen():
# var set
c = self.c
a = self.a
b = self.b
# do sth in f1
xx = a + b
...
str = ...
return str
--genstr.py
from f0 import *
from f1 import *
f = open('xxx.x','w')
f.write(f0.gen(1)+f1.gen(1))
f.close()
The code is slightly confusing and i'm not sure what exactly you are trying, but here are some general help.
This code:
for row in csv.DictReader(open(csv_f)):
c += row['x']
Will append the content of coloumn x to c.
import conf
class f0(conf):
def __init__(self, csv_f):
super(f0,self).__init__(self) # Fixed
self.csv_f = csv_f
def gen(self):
# var set
c = self.c #
a = self.a
b = self.b
# do sth in f0
xx = a + b
Instead of c = self.c you can use self.c where ever you need c.
from f0 import *
from f1 import *
f = open('xxx.x','w')
f.write(f0(filename).gen()+f1(filename).gen())
f.close()
Your first two functions differ in these lines:
xx = a + b
vs.
xx = a*b + b
You can pass in this as as additional argument.
def gen(csv_f, f):
# var define
for row in csv.DictReader(open(csv_f)):
c += row['x']
...
a = 1
b = 2
...
# use the passed function
xx = f(a, b)
...
return str
l0 = lambda a, b: a + b
l1 = lambda a, b: a * b + b
# call gen with one of these lambdas as the second argument
gen(1, l0)
gen(1, l1)

Categories

Resources