Small python program involving newton method - python

I'm trying to write a small program in python that involves(among other things) Newton method, but i'm encountering several problems, that are probably pretty basic, but since I'm new at programming, i cant overcome..
First i defined the function and it's derivative:
import math
def f(x,e,m):
return x-e*math.sin(x)-m
def df(x,e):
return 1-e*math.cos(x)
def newtons_method(E0,m,e,q):#q is the error
while abs(f(E0,e,m))>q:
E=E0-f(E0,e,m)/df(E0,e)
E0=E
return (E0)
def trueanomaly(e,E):
ta=2*math.arctan(math.sqrt((1+e)/(1-e))*math.tan(E))
return (ta)
def keplerianfunction(T,P,e,K,y,w):
for t in frange (0,100,0.5):
m=(2*math.pi*((t-T)/P))
E0=m+e*math.sin(m)+((e**2)/2)*math.sin(2*m)
newtons_method(E0,m,e,0.001)
trueanomaly(e,E0)
rv=y+K*(e*math.cos(w)+math.cos(w+ta))
return (ta)","(rv)
def frange(start, stop, step):
i = start
while i < stop:
yield i
i += step
The question is that this keeps giving me errors, indentation errors and stuff, especially in the keplerianfunction ... Can someone help me? what am I doing wrong here?
Thank you in advance!

Many things are wrong with this code, and I don't know what the desired behaviour is so can't guarantee that this will help, but I'm going to try and help you debug (although it looks like you mostly need to re-read your Python coursebook...).
First, in most languages if not all, there is a thing called the scope: a variable, function, or any other object, exists only within a certain scope. In particular, variables exist only in the scope of the function that they are defined in. This means that, to use the result of a function, you first need to return that result (which you are doing), and when you call that function you need to store that result into a variable, for example ta = trueanomaly(e, E0).
Then, you don't really need to use brackets when returning values, even if you want to return multiple values. If you do want to return multiple values though, you just need to separate them with a comma, but not with a string character of a comma: write return ta, rv instead of return ta","rv.
Finally, you seem to be iterating over a range of values, yet you don't return the whole range of values but either the first value (if your return is in the for loop), or the last one (if your return is under the for loop). Instead, you may want to store all the ta and rv values into one/two lists, and return that/those lists in the end, for example:
def keplerianfunction(T,P,e,K,y,w):
# Initialise two empty lists
tas = []
rvs = []
for t in frange (0,100,0.5):
m = 2*math.pi*((t-T)/P)
E0 = m+e*math.sin(m)+((e**2)/2)*math.sin(2*m)
E0 = newtons_method(E0,m,e,0.001)
ta = trueanomaly(e,E0)
rv = y+K*(e*math.cos(w)+math.cos(w+ta))
# At each step save the value for ta and rv into the appropriate list
tas.append(ta)
rvs.append(rv)
# And finally return the lists
return (tas,rvs)
Also, why using a new frange function when range has the exact same behaviour and is probably more efficient?

Related

Alternating the use of classes/globals with closures in Python

I came across closures in python, and I've been tinkering around the subject.
Please Correct me if I'm wrong here, but what I understood for when to use closures (generally) is that it can be used as a replacement of small classes (q1) and to avoid the use of globals (q2).
Q1: [replacing classes]
Any instance created from the datafactory class will have it's own list of data, and hence every appending to that object's list will result in an incremental behavior. I understand the output from an OO POV.
class datafactory():
def __init__(self):
self.data = []
def __call__(self, val):
self.data.append(val)
_sum = sum(self.data)
return _sum
incrementwith = datafactory()
print(incrementwith(1))
print(incrementwith(1))
print(incrementwith(2))
OUTPUT:
1
2
4
I tried replacing this with a closure, it did the trick, but my understanding to why/how this is happening is a bit vague.
def data_factory():
data = []
def increment(val):
data.append(val)
_sum = sum(data)
return _sum
return increment
increment_with = data_factory()
print(increment_with(1))
print(increment_with(1))
print(increment_with(2))
OUTPUT:
1
2
4
What I'm getting is that the data_factory returns the function definition of the nested increment function with the data variable sent along as well, I would've understood the output if it was something like this:
1
1
2
But how exactly the data list persists with every call?
Shouldn't variables defined in a function die after the function finishes execution and get regenerated and cleared out with the next fn call?
Note: I know that this behavior exists normally in a function defined with default parameters like def func(val, l = []): where the list will not be cleared on every fn call, but rather be updated with a new element/append, which is also something that I do not fully understand.
I would really appreciate an academic explanation to what happens in both scenarios (OO and closures).
Q2: [replacing use of global]
Is there a way using closures to increment the following variable without using globals or a return statement ?
a = 0
print("Before:", a) # Before: 0
def inc(a):
a += 1
print("After:", a) # After: 0
Thank you for your time.
For the first question, I found after some digging that passing mutables as default parameters isn't really a good move to make:
https://florimond.dev/blog/articles/2018/08/python-mutable-defaults-are-the-source-of-all-evil/#:~:text=of%20this%20mess.-,The%20problem,or%20even%20a%20class%20instance.

At which level do return statements need to be written if there is also an 'if' statement above it?

Let me preface this by saying that I am fairly new to coding, so be gentle.
I have been writing the following:
def execute_move(player, house_choice, houses):
next_house=houses[house_choice]
chosen_house=houses[house_choice-1]
chosen_house_seeds=chosen_house[1]
for i in range(chosen_house_seeds):
if player=='P1': # skips the store of the opposite player
if next_house==houses[13]:
next_house_index=houses.index(next_house)
new_nhi=next_house_index+1
next_house=houses[new_nhi]
elif player=='P2':
if next_house==houses[6]:
next_house_index=houses.index(next_house)
new_nhi=next_house_index+1
next_house=houses[new_nhi]
[(next_house[0], (next_house[1]+1)) if x==next_house else x for x in houses]
next_house_index=houses.index(next_house)
new_nhi=next_house_index+1
next_house=houses[new_nhi]
[(chosen_house[0], (chosen_house[1]-chosen_house_seeds)) if x==chosen_house else x for x in houses]
return houses
My aim is to replace some of the tuples in the list 'houses', and then return the new list of tuples.
For some reason the var I assign the call to later on in the code only produces the original list when printed.
Im thinking that it may have something to do with the indentation of the 'if' statements or the indentation of the return statement.
Help much appreciated!
There is nothing intrinsically special about a return statement when compared to other Python statements.
As such, you should indent it as you would any other (i.e., by placing the return line inside the block that makes sense in your specific algorithm).
Any control structure (such as an if statement) expects the next line to be indented. The control structure ends when the code begins to go back to the original level
https://docs.python.org/3/reference/lexical_analysis.html#indentation
def function(input):
input = input * 2
if (input > 5):
input * 2
return input
input +1
return input
so in this case, if the input is < 2.5, it is returned doubled + 1. If it is >2.5 it is returned * 4
It is probably just a bad cut and paste, but the body of the function must be indented as well.
Your list comprehension statements, those that look like [(next_house[0], ...] are building lists but it's not assigned to anything so it is being discarded. Try setting result to a variable...
list_of_next_house_tuples = [(next_house[0], (next_house[1]+1)) if x==next_house else x for x in houses]
Then determine what you are going to do with this new list.

How to create a list containing names of functions?

I am completely new to python and programming but I am trying to learn it using more practical approach.
What I am trying to do is an exercise for converting different units, e.g. pounds -> kilograms, foot -> meter etc.
I have defined all the functions for different unit pairs:
def kg_to_g(value):
return round(value*1000.0,2)
def g_to_kg(value):
return round(value/1000.0,2)
def inch_to_cm(value):
return round(value*2.54,2)
def cm_to_inch(value):
return round(value/2.54,2)
def ft_to_cm(value):
return round(value*30.48,2)
etc.
and created a list with names of these functions:
unit_list = ['kg_to_g','g_to_kg','inch_to_cm','cm_to_inch',
'ft_to_cm','cm_to_ft','yard_to_m','m_to_yard',
'mile_to_km','km_to_mile','oz_to_g','g_to_oz',
'pound_to_kg','kg_to_pound','stone_to_kg','kg_to_stone',
'pint_to_l','l_to_pint','quart_to_l','l_to_quart',
'gal_to_l','l_to_gal','bar_to_l','l_to_bar']
The program should randomly choose a unit pair(e.g. kg->pounds) and value (e.g. 134.23), and the user will be asked to convert those values.
random_unit = random.choice(unit_list)
lower = 0.1001
upper = 2000.1001
range_width = upper - lower
ranval = round(random.random() * range_width + lower, 2)
When user enters answer, the program should compare answer with the calculations defined by function and tell user if it is a correct answer or wrong answer:
def input_handler(answer):
if answer == random_unit(ranval):
label2.set_text("Correct!")
else:
label2.set_text("Wrong!")
Unfortunately, that way program doesn't work, and codesculptor(codesculptor.org) returns with an error
TypeError: 'str' object is not callable
Could someone please explain to me what is wrong with the code and suggest something to solve the problem.
Because you've enclosed the function names (in the list) in quotes, they have become strings.
Change your list to:
unit_list = [kg_to_g, g_to_kg, inch_to_cm, cm_to_inch,
ft_to_cm, cm_to_ft, yard_to_m, m_to_yard,
mile_to_km, km_to_mile, oz_to_g, g_to_oz,
pound_to_kg, kg_to_pound, stone_to_kg, kg_to_stone,
pint_to_l, l_to_pint, quart_to_l, l_to_quart,
gal_to_l, l_to_gal, bar_to_l, l_to_bar]
And now it is a list of functions, which can be called like this: unit_list[0](34), for example.
So now random_unit(ranval) should not throw an exception.
Note also that comparing floats (if answer == random_unit(ranval)) will most likely cause you problems. See Is floating point math broken? for some detailed explanations of why this is.
As you are rounding you may get away with it, but it's good to be aware of this and understand that you need to deal with it in your code.
I think this is what you are asking about. You should be able to store the functions in a list like this
unit_list = [kg_to_g, g_to_kg, inch_to_cm, cm_to_inch, ft_to_cm]
You can then call each item in the list and give it a parameter and it should execute the function for example like this:
unit_list[0](value)

Using local variables outside their functions

I understand that functions are useful for code which will be used multiple times so I tried creating a function to save myself time and make my code look neater. The function I had looks like this:
def drawCard():
drawnCard = random.choice(cardDeck)
adPos = cardDeck.index(drawnCard)
drawnCardValue = cardValues[adPos]
However, I am not sure how to return these variables as they are local(?). Therefore, I can not use these variables outside the function. I am just wondering if someone could help edit this function in a way where I could use the drawnCard and drawnCardValue variables outside the function?
Use return:
def drawCard():
drawnCard = random.choice(cardDeck)
adPos = cardDeck.index(drawnCard)
drawnCardValue = cardValues[adPos]
return drawnCard, drawnCardValue
drawnCard, drawnCardValue = drawnCard()
Note, you could also write drawCard this way:
def drawCard():
adPos = random.randrange(len(cardDeck))
drawnCard = cardDeck[adPos]
drawnCardValue = cardValues[adPos]
return drawnCard, drawnCardValue
These two functions behave differently if cardDeck contains duplicates, however. cardDeck.index would always return the first index, so drawnCardValue would always correspond to the first item which is a duplicate. It would never return the second value (which in theory could be different.)
If you use adPos = random.randrange(len(cardDeck)) then every item in cardValue has an equal chance of being selected -- assuming len(cardValue) == len(cardDeck).

Python Newbie: Returning Multiple Int/String Results in Python

I have a function that has several outputs, all of which "native", i.e. integers and strings. For example, let's say I have a function that analyzes a string, and finds both the number of words and the average length of a word.
In C/C++ I would use # to pass 2 parameters to the function. In Python I'm not sure what's the right solution, because integers and strings are not passed by reference but by value (at least this is what I understand from trial-and-error), so the following code won't work:
def analyze(string, number_of_words, average_length):
... do some analysis ...
number_of_words = ...
average_length = ...
If i do the above, the values outside the scope of the function don't change. What I currently do is use a dictionary like so:
def analyze(string, result):
... do some analysis ...
result['number_of_words'] = ...
result['average_length'] = ...
And I use the function like this:
s = "hello goodbye"
result = {}
analyze(s, result)
However, that does not feel right. What's the correct Pythonian way to achieve this? Please note I'm referring only to cases where the function returns 2-3 results, not tens of results. Also, I'm a complete newbie to Python, so I know I may be missing something trivial here...
Thanks
python has a return statement, which allows you to do the follwing:
def func(input):
# do calculation on input
return result
s = "hello goodbye"
res = func(s) # res now a result dictionary
but you don't need to have result at all, you can return a few values like so:
def func(input):
# do work
return length, something_else # one might be an integer another string, etc.
s = "hello goodbye"
length, something = func(s)
If you return the variables in your function like this:
def analyze(s, num_words, avg_length):
# do something
return s, num_words, avg_length
Then you can call it like this to update the parameters that were passed:
s, num_words, avg_length = analyze(s, num_words, avg_length)
But, for your example function, this would be better:
def analyze(s):
# do something
return num_words, avg_length
In python you don't modify parameters in the C/C++ way (passing them by reference or through a pointer and doing modifications in situ).There are some reasons such as that the string objects are inmutable in python. The right thing to do is to return the modified parameters in a tuple (as SilentGhost suggested) and rebind the variables to the new values.
If you need to use method arguments in both directions, you can encapsulate the arguments to the class and pass object to the method and let the method use its properties.

Categories

Resources