Logical Operations and Flow Control

All of the code examples so far perform the same set of operations, in the same order, even if we change the input data.

There are a lot of problems where it is helpful to have the computer perform different operations depending on the specific values of the input data. For example, we might want to write code that has the option of converting from Celsius to Kelvin, or we might want to check our data after we have read it in for any unphysical values—for example, negative temperatures—and raise a warning or automatically exclude the offending data points.

Python provides control statements that allow us to choose the order of execution of our code depending on the value of a logical expression.

One of the most useful control statements is the if statement, which allows us to run a particular piece of code if an expression is true.

A non-programming example might be:

If today is Monday or Friday then I will log on to the CH40208 LOIL at 10:15 am.

Here our expression we consider is:

today is Monday or Friday

If this expression is true then we perform an action.

I will log on to the CH40208 LOIL at 10:15 am.

If this expression is false (and it is not Monday or Friday) we can skip the action and move onto the next thing in our day.

We could make this explicit by defining what we will do if the expression is false:

If today is Monday of Friday then I will log on to the CH40208 LOIL at 10:15 am. Otherwise, I will read my notes.

Now we have two possible actions. One that we do if the expression is true, and one that we do if the expression is false.

Below we will see how to use if statements to construct similar conditional statements in our code. But first we need to look in a bit more detail into how Python decides if something is true or false.

Boolean variable types

We have already met Boolean variable types; these are variables that can be assigned to the special values True or False.

print(True)
True
type(False)
bool
today_is_Friday = True
type(today_is_Friday)
bool

Logical operators

Python provides a number of logical operators that can be used to compare two values, and return a boolean value of True or False.

One example is the equals operator, which is written as ==. This operator can be used to test whether two objects have the same value, e.g.

# test whether 2 is equal to 2
2 == 2
True
# test whether 1 is equal to 2
1 == 2
False
# test whether 2+2 is equal to 4
my_total = 2 + 2
my_total == 4
True

Other operators for “is greater than” &lqduo;is not equal to” “is less than or equal to” are also available

Name

Mathematical Symbol

Operator

Equals

\(=\)

==

Less than

\(<\)

<

Less than or equal

\(\leq\)

<=

Greater than

\(>\)

>

Greater than or equal

\(\geq\)

>=

Not equal

\(\neq\)

!=

# Is 1 greater than 2?
1 > 2
False
# Is Pi (to 2 decimal places) *not* equal to 3?
3.14 != 3
True

== != =

A very common mistake when people are learning Python (and also when they have more experience) is mixing up the == and = operators, which can result in very hard-to-spot bugs. T

  • == is a logical operator that compares two objects to see whether their values are equal.

  • = is the assignment operator, that is used to assign a variable to a value.

This confusing is partly because we might hear people say “a equals b” when describing both the following expressions:

# a equals b?
a == b 

and

# a equals b.
a = b 

A clearer choice of words might be:

# a is equal to b?
a == b 

and

# a is assigned to b 
a = b 
# (or more correctly, a is assigned to the value b has been assigned to)

and and or

Logical statements allow us to express statements like “today is Monday“ or “today is Friday” in code:

today = "Friday" # assignment operator
today == "Monday" # testing equality
False
today == "Friday" # testing equality
True

Python provides and and or keywords that we can use to build compound logical statements. For example

today == "Monday" or today == "Friday"
True
today == "Monday" and today == "Friday"
False

Two logical statements connected by or return True if either of the individual statements return True.

Two logical statements connected by and return True if both of the individual statements return True.

If statements

Now that we can test whether certain conditions are True or False, we can use these conditional statements to control the flow of our code.

An if statement allows us to specify a block of code that will run only if a specific condition is true.

if this_statement_is_true:
    do_something

An if statement starts with the keyword if, followed by a conditional statement, and ending with a colon :.

The lines after the if statement are indented to form a code block. This block of code is executed if the previous condition evaluates to True.

if today == "Monday":
    open_zoom()

In this example, the value of today is equal to the string "Monday" then the following code block runs and the open_zoom() function is called.

If the conditional statement evaluates to False then the indented block is skipped, and execution jumps to the first non-indented line after the if statement.

if this_statement_is_true:
    do_something # this *only* runs if the conditional statement evaluates as `True`.
following_code # this *always* runs, even if the conditional statement evaluates as `False`.

The conditional statement in an if statement can be a compound statement, where the indented code block runs is the entire statement evaluates to True:

if today == "Monday" or today == "Friday":
    open_zoom()

Now, if either today == "Monday" or today == "Friday" are true then the open_zoom() function will be called.

if … else

Let us look at a different example: identifying elements based on their atomic symbol.

my_element = 'F'

if my_element == 'F':
    print('The element is fluorine.')
The element is fluorine.

In this example, we have described what code we would like to run if our conditional statement is True. But if our conditional statement is False then we get no output.

my_element = 'Cl'

if my_element == 'F':
    print('The element is fluorine.')
    # this block is not run, so our code does not print anything.

It would be helpful if we could provide an alternative block of code that will run if our conditional statement is False.

In Python we can use ifelse to do this.

my_element = 'Cl'

if my_element == 'F':
    print('The element is fluorine.')
else:
    print('The element is not fluorine.')
The element is not fluorine.

else is used similarly to our original if statement:. The else has the same indentation as the original if and is followed by a colon :. Then the code block we want to run is indented in a code block below the else: line.

Sometimes we want to test more than one condition in sequence.

If A is true do X.
If A is not true, but B is true do Y.
If neither A nor B are true do Z.

Python allows us to build up these more complex flow control statements by combining if and else with elif (which you can read as short for “else if”)

my_element = 'Cl'

# compound if…elif…else block to test the identify of my_element.
if my_element == 'F':
    print('The element is fluorine.')
elif my_element == 'Cl':
    print('The element is a chlorine.')
else:
    print('I don\'t know what this element is')
The element is a chlorine.

We can have any number of elif statements:

my_element = 'At'

# compound if…elif…else block to test the identify of my_element.
if my_element == 'F':
    print('The element is fluorine.')
elif my_element == 'Cl':
    print('The element is chlorine.')
elif my_element == 'Br':
    print('The element is bromine.')
elif my_element == 'I':
    print('The element is iodine.')
elif my_element == 'At':
    print('The element is astatine.')
else:
    print('I don\'t know what this element is')
The element is astatine.

What if we want to test whether our element is contained in a group? We could do this using or:

if ((my_element == 'F') or 
    (my_element == 'Cl') or 
    (my_element == 'Br') or 
    (my_element == 'I') or 
    (my_element == 'At')):
    print('The element is a halogen.')
elif my_element == 'Ts':
    print('The element is potentially a halogen.')
else:
    print('I don\'t know what this element is')
The element is a halogen.

Here we have wrapped our compound conditional statement in brackets () so that we can split it over multple lines to improve readability.

This code is still quite awkward to read though. And we can start to image the problem if we want to test whether an object is contained in an even larger set. For example, writing a function to test whether a string is a valid chemical symbol!!

def is_an_element(my_element):
    """
    Test whether a string is a valid chemical symbol.
    
    Args:
        my_element (str): The string to test.
        
    Returns:
        (bool): True if the string is a valid chemical symbol; False otherwise.
        
    """
    if ((my_element == 'H') or
        (my_element == 'He') or
        (my_element == 'Li') or
        …
        (my_element == 'Og')): # 118 lines later
        return True
    else:
        return False

Python provides the in keyword as a logical operator for testing whether an object is contained in a sequence, which allows us to write both the examples above much more cleanly.

if my_element in ['F', 'Cl', 'Br', 'I', 'At']:
    print('The element is a halogen.')
elif my_element == 'Ts':
    print('The element is potentially a halogen.')
else:
    print('I don\'t know what this element is')
The element is a halogen.

Here we test whether the value assigned to my_element is contained in the list ['F', 'Cl', 'Br', 'I', 'At'].

Now our function to test whether a string is a valid chemical element becomes

def is_an_element(my_element):
    """
    Test whether a string is a valid chemical symbol.

    Args:
        my_element (str): The string to test.

    Returns:
        (bool): True if the string is a valid chemical symbol; False otherwise.

    """
    valid_symbols = ['H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F' … 'Lv', 'Ts', 'Og']
    if my_element in valid_symbols:
        return True
    else:
        return False

continue and break

Python provides additional flow control statements for controlling the behaviour of loops. For example, if a certain condition is met we might want to jump straight to the next iteration of a loop without running the rest of the current code block, or we might want to quit the loop all together.

  • continue → jump straight to the next iteration of the loop.

  • break → jump out of the loop entirely.

For example, if we are iterating through the numbers 0 to 99, but want to stop if we reach 3, we can use the break keyword:

for i in range(100):
    print(i)
    if i == 3:
        break
print('Loop exited')
0
1
2
3
Loop exited

The range() function used here provides us with a range of numbers that we can iterate through. range(X) gives the sequence of numbers from 0 to X-1; which is analgous to how list indexing works in Python.

Alternatively we might want to count numbers but skip those divisible by 3.

for i in range(20):
    if i%3 == 0: # `True` if i is divisible by 3
        continue
    print(i)
1
2
4
5
7
8
10
11
13
14
16
17
19

Exercises

1. Fizzbuzz

Write a piece of code that can play fizzbuzz. Your code should count from 1 to 100. Numbers that are divisible by 3 are replaced by “Fizz”; numbers that are divisible by 5 are replaced by “Buzz”; and numbers divisible by 3 and 5 are replaced by “Fizz Buzz”.

To generate the numbers from 1 to 100 you can use the range() function with two numbers, to specify the start and end of your range, i.e. range(1,101).

2. Energy minimisation convergence testing

Revise your energy minimisation codes from earlier this week, such that they will automatically break out of the loop when the difference between the previous distance \(r_{\text{old}}\) and the new distance \(r_{\text{new}}\) is less than \(0.02\) Å.

Get your code to report whether your minimisation has convereged, or whether you have reached the maxium number of iterations specified in your loop.

Worked Examples