Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Chapter 3: Control Flow & Logic

Scientific Computing with Python

Learning objectives

By the end of this chapter, you will be able to:

Prerequisites


Why this chapter matters

Programming is fundamentally about teaching computers to make decisions and repeat tasks. When you write an if statement or a loop, you’re translating human logic into instructions a machine can follow. But here’s the critical insight that separates computational thinkers from mere coders: the logic must be designed before it’s implemented. This chapter transforms you from someone who writes code to someone who designs algorithms.

We’ll start with the lost art of pseudocode — not as a bureaucratic exercise, but as the difference between code that works by accident and code that works by design. You’ll learn to recognize five universal algorithmic patterns that appear across scientific computing: accumulation, filtering, mapping, searching, and convergence. These patterns will appear in every project you build, from N-body simulations to data-driven modeling.

A bounded iterative process begins with declared state, computes a residual, accepts only when the residual meets tolerance, otherwise updates state until either convergence or a maximum-iteration failure outcome.

Figure 1:A trustworthy iterative algorithm makes its state, acceptance criterion, and non-convergence outcome explicit.

What to notice: reaching a maximum iteration count is not evidence that a result has converged. It is a separate, reportable outcome with a diagnostic trail.

The control flow structures we explore here are where your numerical calculations from Chapter 2 become dynamic algorithms. Every convergence test, every adaptive timestep, every Monte Carlo acceptance criterion depends on mastering these concepts deeply, not just syntactically. By chapter’s end, you’ll see code not as a sequence of commands, but as a carefully orchestrated flow of decisions and iterations that solve real scientific problems — the same style of thinking used in research pipelines and mission-scale software.

For a first reading, follow this route:

  1. Sketch the algorithm and its state.

  2. Choose a branch and guard its inputs.

  3. Specify a bounded stopping rule.

  4. Investigate a stopped-or-converged result.

The walrus operator, ternary expressions, loop else, nested loops, Welford’s algorithm, debugger commands, and the tables remain part of this chapter’s prose and reference path; they are not hidden prerequisites for the first route.

Try this chapter


3.1 Algorithmic Thinking: The Lost Art of Pseudocode

Most students jump straight from problem to code, then wonder why they spend hours debugging. Professional computational scientists spend more time thinking than typing. Pseudocode is how we think precisely about algorithms without getting distracted by syntax. Think of it as your algorithm’s blueprint — you wouldn’t build a telescope without optical designs, so why write code without algorithmic designs?

Why Pseudocode Matters in Scientific Computing

Consider this scenario: You need to implement adaptive timestepping for an orbital integrator. Without pseudocode, you’ll likely write code, run it, watch orbits spiral incorrectly, debug for hours, and maybe get it working through trial and error. With pseudocode, you’ll identify edge cases, boundary conditions, and logical flaws before writing a single line of Python.

# NOTE: This snippet is intentionally incomplete (placeholder functions/variables)
# and is meant for reading, not running.
# WITHOUT PSEUDOCODE (typical student approach):
# "I'll figure it out as I code..."
def integrate_naive(state, t_end):
    dt = 0.01
    while state.time < t_end:
        new_state = step(state, dt)
        error = estimate_error(state, new_state)
        if error > tolerance:
            dt = dt * 0.5  # Seems reasonable?
        state = new_state
    return state
# Wait, this doesn't work... infinite loop when error is bad!
# Also, dt never increases... hours of debugging ahead

Now let’s see how pseudocode reveals problems immediately! This is exactly how professional scientists and engineers design algorithms: make the logic explicit first, then implement.

The Three Levels of Pseudocode Refinement

Professional algorithm development happens in stages, each revealing different issues. Don’t worry if this feels strange at first — every programmer has felt that way! But once you embrace pseudocode, you’ll save countless hours of debugging. Let’s build this skill together:

Level 1: Conceptual Overview (The Big Picture)

WHILE simulation not done:       # WHILE means "repeat as long as condition is true"
    Take a step
    Check IF step was good        # IF means "only do this when condition is true"
    Adjust timestep

This level helps you understand the overall flow. The WHILE construct creates a loop that continues until some condition becomes false. The IF construct makes a decision based on a condition. Already, we can ask critical questions: What defines “done”? What makes a step “good”? How much should we adjust? These questions matter!

Level 2: Structural Detail (The Flow)

FUNCTION adaptive_integrate(initial_state, end_time):  # FUNCTION groups reusable code
    state <- initial_state                              # <- means "assign value to variable"
    dt <- estimate_initial_timestep(state)

    WHILE state.time < end_time:                       # Loop continues while time hasn't reached end
        DO:                                             # DO-UNTIL creates a loop that runs at least once
            trial_state = integrate(state, dt)
            error = compute_error(trial_state)
            IF error >= tolerance:
                dt <- max(dt * 0.5, dt_min)            # A rejected step changes the next trial
        UNTIL error < tolerance OR dt <= dt_min        # OR means "either condition can be true"

        IF error >= tolerance:
            RETURN failure("minimum timestep reached before an acceptable step")

        state <- trial_state
        dt <- adjust_timestep(error, dt)

    RETURN state                                        # RETURN sends value back to caller

Now we see the retry logic and minimum timestep safeguard. The DO-UNTIL construct ensures we attempt at least one integration step. The OR operator means either condition being true will exit the inner loop. FUNCTION defines a reusable block of code that can be called with arguments and RETURN a result.

Level 3: Implementation-Ready (Stage 1: Core Logic)

FUNCTION adaptive_integrate(initial_state, end_time, tolerance):
    state <- initial_state
    dt <- estimate_initial_timestep(state)

    WHILE state.time < end_time:
        trial_state <- rk4_step(state, dt)
        error <- estimate_error(state, trial_state)

        IF error < tolerance:                          # Decision point
            state <- trial_state
            dt <- min(dt * 1.5, dt_max)                # Can grow
        ELSE:                                          # ELSE handles "otherwise" case
            dt <- max(dt * 0.5, dt_min)                # Must shrink

Level 3: Implementation-Ready (Stage 2: Add Safety)

FUNCTION adaptive_integrate(initial_state, end_time, tolerance):
    state <- initial_state
    dt <- estimate_initial_timestep(state)
    dt_min <- 1e-10 * (end_time - initial_state.time)
    dt_max <- 0.1 * (end_time - initial_state.time)

    WHILE state.time < end_time:
        step_accepted <- False                          # Boolean flag (True/False)
        attempts <- 0

        WHILE NOT step_accepted AND attempts < MAX_ATTEMPTS:  # NOT inverts, AND requires both
            trial_state <- rk4_step(state, dt)
            error <- estimate_error(state, trial_state)

            IF error < tolerance:
                step_accepted <- True
                state <- trial_state
            ELSE:
                dt <- max(dt * 0.5, dt_min)
            attempts <- attempts + 1

        IF NOT step_accepted:
            RETURN failure("no acceptable step within MAX_ATTEMPTS")

        dt <- min(dt * 1.5, dt_max)                    # Grow only after an accepted step

    RETURN state

Each refinement level reveals new issues and solutions. The NOT operator inverts a boolean value (True becomes False, False becomes True). The AND operator requires both conditions to be true. This is computational thinking in action!


3.2 Boolean Logic in Scientific Computing

Every decision in your code ultimately reduces to true or false. But in scientific computing, these decisions often involve floating-point numbers, where equality is treacherous and precision is limited. Let’s master this fundamental building block that underlies everything from data quality checks to convergence criteria!

The Complete Set of Comparison Operators

Python provides six comparison operators that return boolean values (True or False):

Greater than: False
Less than: True
Greater or equal: True
Less or equal: True
Equal to: True
Not equal to: True

Main sequence star? True

The != operator (not equal) is particularly useful for filtering out sentinel values or checking if something has changed. The ability to chain comparisons like 3000 < temperature < 50000 is a Python feature that makes code more readable and matches mathematical notation.

The Three Logical Operators: AND, OR, NOT

Python’s logical operators combine or modify boolean values:

Bright AND variable: False
Bright OR variable: True
NOT bright: False
NOT variable: True

Can observe? True

Truth Table for AND:
      1 AND     1 = True
      1 AND     0 = False
      0 AND     1 = False
      0 AND     0 = False

Special Comparison Operators: is, in

Python has two special operators that are incredibly useful in scientific programming:

a == b: True
a is b: False
a is c: True
Checking None: True

Is 'G' a stellar type? True
Is 'X' a stellar type? False
Is FITS file? True

Handy Reductions: any() and all()

These are extremely common in scientific code: they reduce many boolean checks to one decision.

True
False

The Walrus Operator: Assignment Expressions (Python 3.8+)

Python 3.8 introduced the walrus operator (:=) which allows assignment within expressions:

Large dataset: 123 observations
Large dataset: 123 observations
Note: Walrus operator requires Python 3.8+
It's useful but not essential - all code can be written without it

The Floating-Point Equality Trap

For a computed floating-point quantity, == is often not the comparison you mean—but it remains correct when exact equality is the stated contract. When the question is whether two temperatures agree at a stated resolution, name that resolution and use it explicitly:

Calculated == Expected? False
Tiny difference: 1.00e-10 K
True

Short-Circuit Evaluation: Order Matters!

Python’s and and or operators use short-circuit evaluation — they stop evaluating as soon as the result is determined:

No data or first measurement not positive

Short-circuit OR demonstration:
Result: True
  Running expensive calculation...
Result: True

3.3 Conditional Statements: Teaching Computers to Decide

Conditional statements are where your code makes decisions. In scientific computing, these decisions often involve numerical thresholds, convergence criteria, and boundary conditions. Let’s build your intuition for writing robust conditionals you can trust in long-running analyses and pipelines.

The if Statement: Your First Decision Maker

The if statement is the simplest conditional — it executes code only when a condition is true:

Star is visible to naked eye (mag 4.5)
Massive star detected!
Mass: 10.0 Msun
Will end as supernova

The if-else Statement: Binary Decisions

The else clause provides an alternative when the condition is false:

z = 0.8: distant galaxy
Does not pass this toy screening threshold

The elif Statement: Multiple Choices

The elif (else if) statement allows multiple conditions to be checked in sequence:

white-dwarf outcome (toy model)
white-dwarf outcome (toy model) (near toy threshold)
black-hole outcome (toy model)

Scientific note: This toy classifier uses coarse mass thresholds for pedagogy. Real stellar endpoints depend on additional physics (metallicity, rotation, binarity, mass loss).

Guard Clauses: Fail Fast, Fail Clear

Guard clauses handle special cases immediately, preventing deep nesting and making code clearer. This pattern is essential for scientific code where invalid inputs can cause subtle bugs hours into a simulation!

Earth: 1.00 years
Mercury: 0.24 years
Proxima Centauri b: 0.031 years

The Ternary Operator: Compact Conditionals

Python’s ternary operator provides a compact way to write simple if-else statements:

Star with magnitude 3.5 is visible
Class G star: ~5778K

3.4 Loops: The Heart of Scientific Computation

Now that you’ve mastered making decisions with conditionals, let’s make your code repeat tasks efficiently! Loops are where your programs gain superpowers — they’re the difference between analyzing one star and analyzing millions. Every N-body simulation, every light curve analysis, every Monte Carlo calculation depends on loops. The patterns you learn here will appear in every algorithm you write for the rest of your career.

The for Loop: Iterating Over Sequences

The for loop iterates over any sequence (list, tuple, string, range):

Class O star
Class B star
Class A star
Class F star
Class G star
Class K star
Class M star

Counting with range:
Observation 0
Observation 1
Observation 2
Observation 3
Observation 4

Every 2nd hour from 20:00 to 02:00 (wrapping past midnight):
20:00
22:00
00:00
02:00

The Accumulator Pattern in Scientific Computing

The accumulator pattern is fundamental to scientific computing:

Cluster center of mass: 0.510 pc
Total cluster mass: 6.1 Msun

Common for Loop Patterns

Python provides several useful functions for loop patterns:

Finding bright events with enumerate:
  Alert! Index 4: magnitude 8.2

Parallel iteration with zip:
  t=1s: v=4.9 m/s
  t=2s: v=9.8 m/s
  t=3s: v=14.7 m/s
  t=4s: v=19.6 m/s

Ordinary zip(times, positions) stops at the shorter sequence. That may be convenient for text processing, but it can silently discard a measurement in paired scientific data. Use strict=True when equal lengths are part of the data contract; otherwise check lengths explicitly before iterating.

The while Loop: Conditional Iteration

The while loop continues as long as a condition remains true:

Iteration 0
Iteration 1
Iteration 2
Stopped after 5 iterations: max_iterations_reached; value=59.459

Loop Control: break, continue, and else

Python provides additional loop control statements:

Using break to find the first sample above an illustrative screen:
First sample above this screen: 5.8

Using continue to skip bad data:
Processing: 1.2
Processing: 2.3
Processing: 3.4

Loop else clause:
Target 7 not found in list

The pass Statement: Placeholder

The pass statement does nothing — useful as a placeholder. It should not become a way to hide an unexpected error:

Processing 0
Processing 2
Continued after pass

Nested Loops: Processing 2D Data

Loops can be nested to process multi-dimensional data:

Processing 3x3 pixel grid:
(0,0)=0  (0,1)=1  (0,2)=2  
(1,0)=3  (1,1)=4  (1,2)=5  
(2,0)=6  (2,1)=7  (2,2)=8  

Finding peaks in 2D array:
Peak at (1,1): value=9

3.5 List Comprehensions: Elegant and Efficient

Now that you’ve mastered loops, let’s evolve them into something more compact. List comprehensions can make a simple transformation readable, but performance depends on the operation and workload; clarity is the reason to choose one.

From Loop to Comprehension

Loop result: [0, 4, 16, 36, 64]
Comprehension: [0, 4, 16, 36, 64]

Real Scientific Applications

Observable star count: 6/9
Brightest flux: 1.91e-05
Faintest flux: 9.12e-07

Bright stars dictionary: {'star_0': 12.3, 'star_2': 13.7, 'star_4': 14.5, 'star_6': 11.8, 'star_8': 13.2}

When NOT to Use Comprehensions

Galaxy classifications: ['nearby faint', 'distant bright', 'nearby bright', 'distant faint']

Deep Dive: advanced control flow patterns

Now let’s explore powerful patterns that appear throughout scientific computing. These aren’t just code tricks — they’re fundamental algorithmic building blocks!

Welford’s Algorithm: Numerically Stable Statistics

Stable algorithm: mean=100000000.5, std=1.12
Risky one-pass variance: 2.000e+00
Reference sample variance: 1.250e+00

Why is Welford’s algorithm numerically stable?

The risky one-pass variance formula is mean(x**2) - mean(x)**2: when a large baseline has a small spread, it subtracts two large, nearby quantities. That cancellation can erase the small variance or even produce a negative result from roundoff.

Welford’s algorithm updates the mean from the deviation of each new value from the current mean, then accumulates the corresponding squared deviation. Its advantage is not that floating point cannot represent differences of order one near 108—it generally can—but that it avoids forming variance as a difference of two large aggregate terms.


3.7 Debugging Control Flow

Logic errors are the hardest bugs because the code runs without crashing but produces wrong results. Let’s build your debugging arsenal!

Strategic Print Debugging

Testing convergence:
Iter  0: 0.0000 -> 10.0000 (delta=+10.00000)
Iter  1: 10.0000 -> 19.0000 (delta=+9.00000)
Iter  2: 19.0000 -> 27.1000 (delta=+8.10000)
Iter  5: 40.9510 -> 46.8559 (delta=+5.90490)
Iter 10: 65.1322 -> 68.6189 (delta=+3.48678)
Iter 15: 79.4109 -> 81.4698 (delta=+2.05891)
FAILED after 20 iterations
Caller receives max_iterations_reached after 20 iterations

Using Assertions for Validation

The assert statement helps catch bugs during development:

Average magnitude: 10.33

Main Takeaways

What an incredible journey you’ve just completed! You’ve transformed from someone who writes code line by line to someone who designs algorithms systematically. This transformation mirrors the evolution every computational scientist goes through, from tentative beginner to confident algorithm designer.

You started by learning to think in pseudocode, a skill that gives you the power to design before you code. Those three levels of refinement you practiced are your blueprint for success. Every hour you invest in pseudocode saves many hours of debugging. When you design your next algorithm for analyzing galaxy spectra or simulating stellar evolution, you’ll catch logical flaws on paper instead of after hours of computation.

The complete set of comparison and logical operators you’ve mastered — from simple greater-than checks to complex boolean combinations with and, or, and not — gives you the full vocabulary for expressing any logical condition. You understand that == is dangerous with floats, that is checks identity not equality, and that in elegantly tests membership. These aren’t just syntax details; they’re the building blocks of every data validation, every convergence check, every quality filter you’ll ever write.

Your understanding of conditional statements goes beyond syntax to defensive programming philosophy. Guard clauses help you fail fast and avoid wasting hours (or compute budgets) on invalid inputs. The elif chains you practiced will classify objects, determine processing paths, and control how your code responds to real, messy data.

The loop patterns you’ve mastered are universal across scientific computing. Accumulators power statistics and reductions, convergence loops power solvers, and nested loops show up whenever you traverse grids or images. Whether using for loops to iterate through catalogs, while loops to converge solutions, or list comprehensions to filter data, you now have the core toolkit.

Most importantly, you’ve learned that bugs aren’t failures — they’re learning opportunities. Every infinite loop teaches you about termination conditions. Every off-by-one error reinforces indexing. The debugging strategies you’ve developed, from strategic print statements to assertions, will serve you throughout your career.

Remember that every major computational achievement relies on these fundamentals. You’re not just learning Python syntax — you’re building algorithmic literacy that transfers across domains.


Definitions

Accumulator Pattern: An algorithmic pattern where values are iteratively combined into a running total or aggregate, fundamental to reductions and statistical calculations in scientific data processing.

Adaptive Refinement: A universal pattern where parameters are adjusted based on quality metrics, with safeguards against infinite refinement, appearing in timestepping, mesh refinement, and optimization throughout computational science.

and: Logical operator that returns True only if both operands are true, using short-circuit evaluation.

assert: Statement that raises an AssertionError if a condition is false, used for debugging and documenting assumptions during development (not for production validation).

Boolean Logic: The system of true/false values and logical operations (and, or, not) that underlies all conditional execution in programs.

break: Statement that immediately exits the current loop, skipping any remaining iterations.

Conditional Statement: A control structure (if/elif/else) that executes different code blocks based on whether conditions evaluate to true or false.

continue: Statement that skips the rest of the current loop iteration and proceeds to the next iteration.

elif: “Else if” statement that checks an additional condition when the previous if or elif was false.

else: Clause that executes when all previous if/elif conditions were false, or when a loop completes without breaking.

for: Loop that iterates over elements in a sequence or iterable object.

Guard Clause: A conditional statement at the beginning of a function that handles special cases or invalid inputs immediately, preventing deep nesting.

if: Statement that executes code only when a specified condition is true.

in: Operator that tests membership in a sequence or collection.

is: Operator that tests object identity (same object in memory), not just equality of values.

List Comprehension: A concise Python syntax for creating lists: [expression for item in iterable if condition].

not: Logical operator that inverts a boolean value (True becomes False, False becomes True).

or: Logical operator that returns True if at least one operand is true, using short-circuit evaluation.

pass: Null statement that does nothing, used as a placeholder where syntax requires a statement.

Pseudocode: A human-readable description of an algorithm that focuses on logic and structure without syntactic details.

sentinel value: A special marker value (like -999, ‘END’, or None) that signals the end of data or a special condition, allowing loops to know when to stop processing

Short-circuit Evaluation: The behavior where logical operators stop evaluating as soon as the result is determined.

Walrus Operator (:=): Assignment expression operator (Python 3.8+) that assigns a value to a variable as part of an expression, allowing both assignment and testing in a single statement.

while: Loop that continues executing as long as a specified condition remains true.


Key Takeaways

✓ Pseudocode reveals logical flaws before they become bugs — always design before implementing

✓ Master all six comparison operators (>, <, >=, <=, ==, !=) and three logical operators (and, or, not)

✓ For computed floating-point quantities, use a tolerance chosen for the claim; use == only when exact equality is the stated contract

✓ The is operator checks identity, not equality — use it for None checks

✓ The in operator elegantly tests membership in sequences or strings

✓ Guard clauses handle special cases first, making main logic clearer

✓ for loops iterate over sequences, while loops continue until a condition becomes false

✓ break exits loops early, continue skips to the next iteration, else runs if loop completes

✓ List comprehensions can clarify simple transformations but become unreadable for complex logic; measure performance when it matters

✓ Short-circuit evaluation in and/or prevents errors and improves performance

✓ The accumulator pattern is fundamental to scientific computing, appearing in all statistical calculations

✓ For iterative numerical while loops, include a maximum iteration limit and inspect the returned status

✓ Welford’s algorithm helps avoid a cancellation-prone variance formula in streaming statistics

✓ Use assert statements to document and enforce assumptions during development

✓ Control-flow patterns make assumptions, stopping rules, and failure paths inspectable in scientific software


Reference: quick tables and debugging tools

OperatorDescriptionExample
>Greater thanif magnitude > 6.0:
<Less thanif redshift < 0.1:
>=Greater or equalif snr >= 5.0:
<=Less or equalif error <= tolerance:
==Exact value comparisonif status == 'complete':
!=Not equalif flag != -999:

: Comparison Operators

OperatorDescriptionExample
andBoth must be trueif x > 0 and y > 0:
orAt least one trueif bright or variable:
notInverts booleanif not converged:

: Logical Operators

OperatorDescriptionExample
inMembership testif 'fits' in filename:
isIdentity testif result is None:
is notNegative identityif data is not None:
not inNegative membership testif 'error' not in log_file:
:=Walrus operator (Python 3.8+)if (n := len(data)) > 100:

: Special Operators

StatementPurposeExample
if/elif/elseConditional executionif mag < 6: visible = True
forIterate over sequencefor star in catalog:
whileLoop while condition is truewhile not converged and n < max:
breakExit loop earlyif converged: break
continueSkip to next iterationif bad_data: continue
passDo nothing (placeholder)if not ready: pass
assertDebug checkassert len(data) > 0

: Control Flow Statements

FunctionPurposeExample
range(n)Generate 0 to n-1for i in range(10):
range(start, stop, step)Generate with stepfor i in range(0, 10, 2):
enumerate(seq)Get index and valuefor i, val in enumerate(data):
zip(seq1, seq2, strict=True)Parallel iteration with an equal-length contractfor x, y in zip(xs, ys, strict=True):
len(seq)Sequence lengthfor i in range(len(data)):

: Built-in Functions for Loops

FunctionPurposeExample
all(iterable)All elements trueif all(x > 0 for x in data):
any(iterable)Any element trueif any(x < 0 for x in data):
math.isclose()Float comparison with a stated tolerance contractif math.isclose(a, b, abs_tol=resolution):
math.isfinite()Check not inf/nanif math.isfinite(result):
math.isnan()Check for NaNif not math.isnan(value):
math.isinf()Check for infinityif math.isinf(value):
isinstance()Type checkingif isinstance(x, float):

: Comparison Functions

PatternPurposeStructure
AccumulatorAggregate valuestotal = 0; for x in data: total += x
FilterSelect subset[x for x in data if condition(x)]
MapTransform all[f(x) for x in data]
SearchFind first matchfor x in data: if test(x): return x
ConvergenceIterate to solutionwhile not converged and n < max:
Guard clauseHandle edge casesif invalid: return None
SentinelSignal terminationif value == -999: break

: Common Algorithmic Patterns


Extended reference: operators, methods, and debugger commands

New Built-in Functions

Logical Testing

Loop Support

Control Flow Keywords

Conditionals

Loops

Other

Operators

Comparison

Logical

New Math Module Functions

import math

Debugging Support

IPython Magic Commands

Debugger Commands (when in pdb)


Next Chapter Preview

You’ve conquered control flow — now get ready for the next level! Chapter 4 will reveal how to organize data deliberately using Python’s lists, dictionaries, sets, and tuples. You will connect each choice to an access pattern, mutability question, and measured workload rather than assuming a universal speedup.

Imagine trying to find a specific star in a catalog of millions. A list search and a dictionary lookup make different promises, and their actual costs depend on the operation, data size, hardware, and implementation. Chapter 4 will give you the vocabulary to reason about that tradeoff and measure it honestly.

The control flow patterns you’ve mastered here will operate on the data structures you’ll learn next. Your loops will iterate through dictionaries of astronomical objects. Your conditionals will filter sets of observations. Your comprehensions will transform lists of measurements into meaningful results. Together, control flow and data structures give you the power to handle the massive datasets of modern science — from Gaia’s billion-star catalog to the petabytes of data from the Square Kilometre Array.

Get excited — Chapter 4 is where your code goes from processing dozens of data points to handling thousands efficiently!