Infinite while loop in python - with conflicting conditions - python

my typo error led in the 'while loop' function led me to the following question
from time import sleep
from random import randint
def control_pressure():
pressure = measure_pressure()
while True:
if pressure <= 500:
break
while pressure > 500 and pressure <= 700:
run_standard_safeties()
pressure = measure_pressure()
while pressure > 100: # Here is the typo
run_critical_safeties()
pressure = measure_pressure()
print("Wow! The system is safe...")
def measure_pressure():
pressure = randint(450, 800)
print(f"psi={pressure}", end="; ")
return pressure
def run_standard_safeties():
print("Running standard safeties...")
sleep(0.2)
def run_critical_safeties():
print("Running critical safeties...")
sleep(0.7)
if __name__ == "__main__":
control_pressure()
In the above, I made a typo and checked pressure > 100, while condition. But earlier in the first part of the function, it checked, while pressure <= 500, then break. So technically any value lesser than or equal to 500 should return True and break the function without reaching the pressure > 100 condition.
But this function continued without stopping. Why does it happen?
Thanks

Your scenario is best described sequentially.
Any initial pressure greater than 500 will bypass the break and continue to the first inner loop. Since you're getting a random value 450..800 (a range of 350 - ranges here may be off by one but they're close enough so as not to matter), this has a probability of about 300/350 (the numerator comes from the 500..800 range) or 85%.
The first inner loop will then run as long as you're getting pressures in the range 500..700, a probability of about 200/350, or 57%.
At some point you'll exit that first inner loop and enter the second. And you will enter it because all values in the range 450..800, that you can possibly get as a pressure, are greater than a hundred.
That means, of course, that you will also never exit that loop because no pressure you ever get after that point will be a hundred or less, which is what's needed to exit the loop.
As an aside, you could actually avoid this loops-within-loops structure by realising that you to get the pressure every iteration of every loop, so that can be made common.
Assuming your typo was to use 100 where you meant 700 (as that marches with mutually exclusive ranges), that can be simplified to:
def control_pressure():
while True:
pressure = measure_pressure()
if pressure <= 500:
print("Wow! The system is safe...")
return
if pressure <= 700:
run_standard_safeties()
else:
run_critical_safeties()
Or, if you want to use the := walrus operator(a) (available in Python 3.8+, I think), you could shorten it even further by getting and checking pressure as part of the while statement:
def control_pressure():
while (pressure := measure_pressure()) > 500:
if pressure <= 700:
run_standard_safeties()
else:
run_critical_safeties()
print("Wow! The system is safe...")
(a) So called because, if you rotate it 90° clockwise and squint, it looks like a walrus with its two eyes and two huge tusks.
It basically (in this case) lets you assign a value to a variable as part of checking said variable, so that the value is available for later checks as well.

If the initial number is above 100 and above 500 it will enter the second loop and never stop since it will always be above 100, since your are taking one at random from 450-800.

Related

Run once until conditions change

What I'm trying to accomplish: if the room lights go off, the monitor should dim. If the room lights go back on, the monitor should get brighter.
On a Rpi4B, I'm trying to use ddcutil in combination with a photoresistor and capacitor to automatically adjust monitor brightness depending on ambient light in the room. I really only need 3 brightness settings; one each for bright, average, and dim light conditions.
I've been editing some code I found and I have the code responding to three different levels of brightness (just text verification telling me the if the light is dim, medium, or bright.) The issue is that it keeps printing the result over and over and I only need it to print (or eventually run my ddcutil routine) once, but STILL continue to check the ambient light level and react accordingly. How do I continually check input from the sensor without continually passing an instruction to ddcutil? I know very little about writing code and I suspect a Boolean flag may be the answer that I don't understand. Thanks for any help.
#!/usr/local/bin/python
import RPi.GPIO as GPIO
import time
__author__ = 'Gus (Adapted from Adafruit)'
__license__ = "GPL"
__maintainer__ = "pimylifeup.com"
GPIO.setmode(GPIO.BOARD)
#define the pin that goes to the circuit
pin_to_circuit = 3
def rc_time (pin_to_circuit):
count = 0
#Output on the pin for
GPIO.setup(pin_to_circuit, GPIO.OUT)
GPIO.output(pin_to_circuit, GPIO.LOW)
time.sleep(.2)
#Change the pin back to input
GPIO.setup(pin_to_circuit, GPIO.IN)
#Count until the pin goes high
while (GPIO.input(pin_to_circuit) == GPIO.LOW):
count += 1
return count
#Catch when script is interupted, cleanup correctly
try:
# Main loop
while True:
if(rc_time(pin_to_circuit)) > 4000:
print("Too dark. Frame off.")
if(rc_time(pin_to_circuit)) < 4000 and (rc_time(pin_to_circuit)) > 1000:
print("Good light. Medium brightness.")
if(rc_time(pin_to_circuit)) < 1000:
print("Bright day. Maximum brightness set.")
except KeyboardInterrupt:
pass
finally:
GPIO.cleanup()
I would store the last light value in a variable and in your loop compare that to the current value. I would also move the checking logic to it's own method so it can be re-used elsewhere if required.
def get_light_value():
pin_rc_time = rc_time(pin_to_circuit)
if pin_rc_time > 4000:
return "low"
elif pin_rc_time <= 4000 and pin_rc_time > 1000:
return "medium"
else:
return "high"
Notice here that we also only call rc_time once per loop. That will avoid any weirdness if the value changes while the conditions are being determined. I changed the logic to check for <= 4000 which will cover the missing condition in your code where the value is exactly 4000. And I also used elif/else to ensure only one branch is evaluated.
Using this in the main loop:
last_light_value = None
curr_light_value = None
while True:
last_light_value = curr_light_value
curr_light_value = get_light_value()
# Do nothing if the value did not change
if last_light_value == curr_light_value:
time.sleep(1)
continue
if curr_light_value == "low":
print("Too dark. Frame off.")
elif curr_light_value == "medium":
print("Good light. Medium brightness.")
else: # Value must be high.
print("Bright day. Maximum brightness set.")
Homework for you:
To take this further, you might also want to think about what will happen if the pin value is very close to the threshold of the cutoffs. This will most likely cause the value to flip back and between categories, as some sensors are not extremely precise, and things like florescent lighting can oscillate in intensity.
To combat this, you could implement a filter that to only change the value if it is in a different category and has at least changed some delta of value since the last check. You can use a similar trick to the while loop which takes into account the last value seen before making a determination on if a value should be changed.

Is there a way to have a function invoke 'continue' affecting a loop in its caller?

I am trying to refactor a code that consists of nested loops. For a similar repetitive code snippet, I am trying to write a function which consists of a 'continue' within. The loop is very long and I just want to include the if statement in the function.
def calc_indexes_for_rand_switch_on(self, switch_on, rand_time, max_free_spot, rand_window):
if np.any(self.daily_use[switch_on:rand_window[1]] != 0.001): # control to check if there are any other switch on times after the current one
next_switch = [switch_on + k[0] for k in np.where(self.daily_use[switch_on:] != 0.001)] # identifies the position of next switch on time and sets it as a limit for the duration of the current switch on
if (next_switch[0] - switch_on) >= self.func_cycle and max_free_spot >= self.func_cycle:
upper_limit = min((next_switch[0] - switch_on), min(rand_time, rand_window[1] - switch_on))
elif (next_switch[0] - switch_on) < self.func_cycle and max_free_spot >= self.func_cycle: # if next switch_on event does not allow for a minimum functioning cycle without overlapping, but there are other larger free spots, the cycle tries again from the beginning
continue
else:
upper_limit = next_switch[0] - switch_on # if there are no other options to reach the total time of use, empty spaces are filled without minimum cycle restrictions until reaching the limit
else:
upper_limit = min(rand_time, rand_window[1] - switch_on) # if there are no other switch-on events after the current one, the upper duration limit is set this way
return np.arange(switch_on, switch_on + (int(random.uniform(self.func_cycle, upper_limit)))) if upper_limit >= self.func_cycle \
else np.arange(switch_on, switch_on + upper_limit)
This is the function I am trying to write. The if statement is a part of bigger while loop in the main code. But, here this gives an error since there is no loop. How can I solve this?
You can return a sentinel value here, e.g. None and handle this in the caller.
So in your function:
elif (next_switch[0] - switch_on) < self.func_cycle and max_free_spot >= self.func_cycle: # if next switch_on event does not allow for a minimum functioning cycle without overlapping, but there are other larger free spots, the cycle tries again from the beginning
return None
And use it like:
while some_condition():
# do some stuff
...
result = calc_indexes_for_rand_switch_on(switch_on, rand_time, max_free_spot, rand_window)
if result is None:
continue
...
# do stuff with result

Where does the problem or bug lie in the logic of this while loop Machine Learning Cubic Root-Finding algorithmic code that calls a function?

Goal: The goal of this program is to find additional accelerators to the convergence process of cubic or n-polynomial root finding methods/algorithms.
Problem: The code can not tabulate one variable from the upper section of while loops for some unknown reason I can't delineate. Variable "tbl_val_6" is not defined despite it being clearly defined above (in the code) in the sixth loop. (See sixth line in "Tabulation" code...)
Tabulation:
z = [('Halley (Lower)','Original',act_rad,tbl_val_1**(10**9),iter_num_1),
('Halley (Upper)','Original',act_rad,tbl_val_2*(10**9),iter_num_3),
('Bisection','Original',act_rad,tbl_val_3*(10**9),iter_num_3),
('Regula Falsi','Original',act_rad,tbl_val_4*(10**9),iter_num_4),
('Secant','Original',act_rad,tbl_val_5*(10**9),iter_num_5),
('Halley (Lower)', 'Version 2',act_rad,tbl_val_6*(10**9),iter_num_6),
('Halley (Upper)','Original',act_rad,tbl_val_7*(10**9),iter_num_7),
('Bisection','Version 2',act_rad,tbl_val_8*(10**9),iter_num_8),
('Regula Falsi','Version 2',act_rad,tbl_val_9*(10**9),iter_num_9),
('Secant','Version 2',act_rad,tbl_val_10*(10**9),iter_num_10),
('Halley (Lower)', 'Version 3', act_rad,tbl_val_11*(10**9),iter_num_11),
('Halley (Upper)','Version 3', act_rad,tbl_val_12*(10**9),iter_num_12),
('Bisection','Version 3',act_rad,tbl_val_13*(10**9),iter_num_13),
('Regula Falsi','Version 3',act_rad,tbl_val_14*(10**9),iter_num_14),
('Secant','Version 3',act_rad,tbl_val_15*(10**9),iter_num_15),]
tbl = tabulate(z, headers=['Numerical Method','Version','Act. Val' 'Approx. Val','#Iterations'],
tablefmt='fancy_grid')
print(tbl)
print("***Where Version 2: Iterative Root and Version 3: Reciprocal Factorial: Convergence
Accelerators")
Associated Error:
NameError Traceback (most recent call last)
<ipython-input-1-657a5a0a1bbf> in <module>
460 ('Regula Falsi','Original',act_rad,tbl_val_4*(10**9),iter_num_4),
461 ('Secant','Original',act_rad,tbl_val_5*(10**9),iter_num_5),
--> 462 ('Halley (Lower)', 'Version 2',act_rad,tbl_val_6*(10**9),iter_num_6),
463 ('Halley (Upper)','Original',act_rad,tbl_val_7*(10**9),iter_num_7),
464 ('Bisection','Version 2',act_rad,tbl_val_8*(10**9),iter_num_8),
NameError: name 'tbl_val_6' is not defined
This is the sixth loop out of fifteen that computes the iteration and approximate cubic root. It can not define "tbl_val_6" because the variable defining it "x_new_low_H_2" can not be defining meaning it is most likely having issues calling the functions outside of its loop structure. Or I think it is, however, why did the others have no issue?:
Sixth Loop (where "tbl_val_6" id defined) and it and all 15's initial conditions:
Ini.Cond.
#Root-Finding Method Initial Conditions
crit_lim_low = 1000
crit_lim_up = 1000
itr = 0
Sixth Loop (out of 15):
# Halley's (Lower)
while crit_lim_low > .0005:
x_new_low_H_2 = guess_low - ((2*f(guess_low)*df(guess_low))/((2*(df(guess_low))**2)-
(f(guess_low)*ddf(guess_low))))
crit_lim_low = abs(act_rad - x_new_low_H_2)
if crit_lim_low <= .0005:
itr += 1
iter_num_6 = itr #iter number for table
itr = 0 #Re-initializion
crit_lim_low = 1000
tbl_val_6 = x_new_low_H_2 #value of approx. root (table)
guess_low = act_rad - 10
break
elif itr >= 1000:
print("Lower Guess Halley's Method Failed(V.2): Check parameters.")
itr = 0
crit_lim_low = 1000
guess_low = act_rad - 10
break
else:
itr += 1
j = x_new_low_H_2**(1/itr)
guess_low = x_new_low_H_2 + j
Functions f(x), df(x), and ddf(x) are functions defined above and OUTSIDE of the loop structure; initial conditions and constraints shown for reproducibility:
Input/Initial Conditions/Functions:
print('User Assumptions/Limitations:')
print('\n')
print('1. Radial Upper Limit must be no more than 100 nanometers')
print('and Radial Lower Limit must be no less than 1 nanometer.')
print('2. Period of diffusion must not exceed 24 hours.')
print('3. Diffusion Rate should be reasonable a number of radial')
print('units (nm) per hour as to not supersede other constraints.***')
print('\n')
print('***Note: r = (dr/dt)t must be between RUL and RLL only!')
print('\n')
ul_rad = (10**(-9))*float(input('What is the radial upper limit (nm)? '))
ll_rad = (10**(-9))*float(input('What is the radial lower limit (nm)? '))
diff_t = (10**(-9))*float(input('What is the period of diffusion (hours)? '))
dr_dt = (10**(-9))*float(input('What is the volume rate of diffusion (nm/hour)? '))
#Formula Set-Up
pi = 3.14159265359
import math
#Prelim Calculation
act_rad = math.sqrt((((4/3)*pi*((ul_rad)**3)) - ((4/3)*pi*(dr_dt)*((diff_t)**3)) + ((4/3)*pi*
((ll_rad)**3)))*(3/(4*pi)))
guess_low = act_rad - 10
guess_up = act_rad + 10
##Functions for Root-Finding Methods
def f(x): #volume
return(((4/3)*pi*((ul_rad)**3)) - ((4/3)*pi*(x**3)) - ((4/3)*pi*(dr_dt)*((diff_t)**3)) +
((4/3)*pi*((ll_rad)**3)))
def df(x):#surface area
return((4*pi*((ul_rad)**2)) - (4*pi*(x**2)) - (4*pi*(dr_dt)*((diff_t)**2)) + (4*pi*
((ll_rad)**2)))
def ddf(x):#mean width times curvature
return(((8*pi*ul_rad) - (8*pi*x) - (8*pi*(dr_dt)*(diff_t)) + (8*pi*ll_rad)))
If this needs a global function for "x" I'm not sure how it would since it should be flagged at the first loop instead of the sixth. Re-initialization of critical values, high and low guesses, and etc. looks fine as well as the "tabulate" function. Can anyone see what I am missing? Please be easy on me as I am a novice in Python if the answer is obvious.
Debugging/Self-Deliberation Notes:
Yesterday and this morning I tried to make sure my re-initialization was fine and triple-checked. I also printed "tbl_val's" and "iter_num's" from various loops and none of the loops are iterating or the values are too small for the radii, it seems. This may be an overall issue with the logic of all 15 separate WHILE loops and I'm not sure what's going on as I'm taking care to re-initialize. I have a hunch its having issues calling the outside functions but it should've had issues at the start in the FIRST loop if that were true.
Note: I tried to minimize the code as much as I could but Tabulate, the sixth WHILE loop, and the functions as well as the inputs and initial conditions are KEY and MANDATORY for that output "tbl_val_6". Also, if it is still unclear as to what I am trying to do or more code needs to be seen, I will edit this immediately and as soon as I can!
Are you sure of the value of crit_lim_low ?
You define tbl_val_6 in an if clause in a while loop with both the loop and the if clause dependent on the value of crit_lim_low.
If crit_lim_low is <= 0.0005 then the while loop wont run at all, and you wont define your variable.

Using return instead of yield

Is return better than yield? From what ive read it can be. In this case I am having trouble getting iteration from the if statement. Basically what the program does is take two points, a begin and end. If the two points are at least ten miles apart, it takes a random sample. The final if statement shown works for the first 20 miles from the begin point, begMi. nCounter.length = 10 and is a class member. So the question is, how can I adapt the code to where a return statement would work instead of a yield? Or is a yield statement fine in this instance?
def yielderOut(self):
import math
import random as r
for col in self.fileData:
corridor = str(col['CORRIDOR_CODE'])
begMi = float(col['BEGIN_MI'])
endMi = float(col['END_MI'])
roughDiff = abs(begMi - endMi)
# if the plain distance between two points is greater than length = 10
if roughDiff > nCounter.length:
diff = ((int(math.ceil(roughDiff/10.0))*10)-10)
if diff > 0 and (diff % 2 == 0 or diff % 3 == 0 or diff % 5 == 0)\
and ((diff % roughDiff) >= diff):
if (nCounter.length+begMi) < endMi:
vars1 = round(r.uniform(begMi,\
(begMi+nCounter.length)),nCounter.rounder)
yield corridor,begMi,endMi,'Output 1',vars1
if ((2*nCounter.length)+begMi) < endMi:
vars2 = round(r.uniform((begMi+nCounter.length),\
(begMi+ (nCounter.length*2))),nCounter.rounder)
yield corridor,begMi,endMi,'Output 2',vars1,vars2
So roughdiff equals the difference between two points and is rounded down to the nearest ten. Ten is then subtracted so the sample is taken from a full ten mile section; and that becomes diff. So lets say a roughDiff of 24 is rounded to 20, 20 - 10, diff + begin point = sample is taken from between mi 60 and 70 instead of between 70 and 80.
The program works, but I think it would be better if I used return instead of yield. Not a programmer.
return is not better, it's different. return says "I am done. Here is the result". yield says "here is the next value in a series of values"
Use the one that best expresses your intent.
Using yield makes your function a generator function, which means it will produce a series of values each time its (automatically created) next() method is called.
This is useful when you want to process things iteratively because it means you don't have to save all the results in a container and then process them. In addition, any preliminary work that is required before values can generated only has to be done once, because the generator created will resume execution of your code following the that last yield encountered — i.e. it effectively turns it into what is called a coroutine.
Generator functions quit when they return a value rather than yield one. This usually happens when execution "falls off the end" when it will return None by default.
From the looks of your code, I'd say using yield would be advantageous, especially if you can process the results incrementally. The alternative would be to have it store all the values in a container like a list and return that when it was finished.
I use yield in situations where I want to continue iteration on some object. However, if I wanted to make that function recursive, I'd use return.

python loop only print values once

Below is a short piece of python code.. it's for a sensor reading light or darkness. What I want it to do when the value drops below 500 print "light" and when it's above 500 print "Dark". This pretty much works but the text is repeated for every reading.. while I only want it to be printed upon a change.. anyone has any idea how to do this? I did quite a bit of shell programming.. but somehow this simple issue I can't get it done in python..
#!/usr/bin/python
import RPi.GPIO as GPIO, time
GPIO.setmode(GPIO.BCM)
# Define function to measure charge time
def RCtime (PiPin):
measurement = 0
# Discharge capacitor
GPIO.setup(PiPin, GPIO.OUT)
GPIO.output(PiPin, GPIO.LOW)
time.sleep(0.1)
GPIO.setup(PiPin, GPIO.IN)
# Count loops until voltage across
# capacitor reads high on GPIO
last = 9
while (GPIO.input(PiPin) == GPIO.LOW):
measurement += 1
if measurement < 500:
print last
if last == 0:
print "light"
last = 1
if measurement >500:
print "dark"
last = 0
print last
return measurement
# Main program loop
while True:
print RCtime(4) # Measure timing using GPIO4
I think you have the idea right in the if measurement < 500: condition, printing "light" only when last was different. You just have to repeat similar logic in the > 500 condition. But the real problem is that last here is a local variable, so the value will get reset to 9 on every call. So you need to remove the last=9, and define last outside of the function and declare it as global inside the function:
#in main program
last = 9
def RCTime ...:
global last
....
Since you're returning value on each function execution I think the best idea would be to compare it in the while loop and keep the function only for getting the data.
Something along these lines:
previous, current = None, None
def RCtime (PiPin):
[...]
while True:
measurement = RCtime(4)
previous, current = current, measurement<500
if current != previous:
# value changed...
# `current` is bool and True if there is light.
# or you can just deal with the value itself for logic
print measurement
Otherwise returning the value of last and then passing it on when function is next time called is also an acceptable solution.
I simulate the sensor's readings using a list named readings, the rest of the code can be directly translated to your use case
readings = [ 600, 600, 550, 501, 450, 400, 400, 460, 520, 600, 600]
dark = None
for i, reading in enumerate(readings):
dark_now = 'Dark' if reading < 500 else 'Light'
if dark_now != dark:
dark = dark_now
print i, reading, dark
Output
0 600 Light
4 450 Dark
8 520 Light

Categories

Resources