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 4: Data Structures - Organizing Scientific Data

Scientific Computing with Python

Learning objectives

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

Prerequisites

Why this chapter matters

Imagine that a simulation must repeatedly answer three different questions: Which measurements arrived first? Which particle has this identifier? Which particles have already been processed? Those questions ask for order, keyed lookup, and uniqueness—not one generic container. The representation you choose determines what your program can ask efficiently and what kinds of accidental mutation it permits.

This chapter transforms you from someone who stores data to someone who organizes it deliberately for scientific computing. You’ll discover why dictionary and set membership are usually fast, when their hash-table assumptions matter, and how to measure a specific workload rather than inheriting a performance slogan. You will also learn why two names can quietly refer to the same mutable state—and how to make that ownership decision visible.

These concepts prepare you for numerical computing ahead. Typed, contiguous arrays can offer a different memory and execution model from Python object containers, but the size of that advantage depends on the operation, hardware, dtype, and baseline. Immutability and explicit ownership also prepare you to reason about stateful and functional interfaces. By chapter’s end, you will be able to make a representation choice, state its trade-off, and test the assumption when it matters.

In Chapter 3, you learned how algorithms flow through decisions and loops. In Chapter 4, you’ll learn how algorithms scale—because data structures determine whether your logic runs in seconds or in days.

A logarithmic plot compares illustrative constant, logarithmic, linear, n log n, and quadratic operation-count models as input size grows. An annotation notes that quadratic all-pairs work reaches one trillion pair checks at one million inputs.

Figure 1:Scaling is a model of computational work, not a timing result from one machine: the operation count can become infeasible long before a calculation runs.

What to notice: this is a Python-produced plot, but not a new plotting API to learn yet. Read it as a model of operation counts; Chapter 8 teaches how to construct and scrutinize a plot. Actual runtime also depends on the workload, implementation, data layout, hardware, and measurement method.

For a first reading, follow this route:

  1. Match a structure to an access pattern.

  2. Choose ordered or immutable sequences.

  3. Trace mutation and aliasing.

  4. Use keyed lookup and set membership carefully.

  5. Make and justify a combined choice.

For a return visit, use the memory, caches, and benchmark Deep Dive or the structures and operations Reference. They remain part of this chapter, not hidden prerequisites for the first route.

Try this chapter

4.1 What Is a Data Structure?

A data structure organizes information to match an access pattern. In an N-body simulation, you might store particles in creation order (a list), map a known particle ID to its properties (a dictionary), or record which IDs have already been processed (a set). A spatial-index algorithm may additionally organize particles into cells or a tree for a neighbor query. The container is one ingredient in that algorithm; it does not by itself turn an all-pairs calculation into a tree code.

Quick Preview: Python’s Core Data Structures

StructureMutableOrderedDuplicatesUse Case
listYesYesYesParticle arrays, time series
tupleNoYesYesConstants, coordinates, dict keys
dictYesYes*Keys: NoLookup tables, properties
setYesNoNoUnique particles, membership

*Dicts maintain insertion order (Python 3.7+ language guarantee)

Decision Framework: Choosing Your Data Structure

Before diving into each structure, here’s the mental model for choosing:

Need ordered sequence with duplicates?
├── Yes -> Need to modify it?
│         ├── Yes -> list
│         └── No (constants/config) -> tuple
└── No -> Need key-value mapping?
          ├── Yes -> dict (average-case O(1) lookup by key)
          └── No -> Need uniqueness/set math?
                    ├── Yes -> set (average-case O(1) membership)
                    └── No -> Probably list

Key invariants to remember:

StructureMutable?Keys/Elements Must Be...When to Use
listYesAnythingOrdered data, will modify
tupleNoAnythingConstants, dict keys, records
dictYesHashableFast lookup by key
setYesHashableUniqueness, membership tests

Building Intuition: Measuring Speed Empirically

Ready to discover something that will transform how you think about organizing data in your simulations? We’re going to measure the dramatic performance difference between different data structures. This empirical approach - measure first, understand why, then optimize - is exactly how computational scientists approach performance optimization.

Membership testing: list (linear scan) vs set (average-case hash lookup)
=======================================================
      Size   List (mus)    Set (mus)    Speedup
-------------------------------------------------------
       100         0.60         0.04         16x
     1,000         6.87         0.04        187x
    10,000        61.80         0.03      1,829x
   100,000       619.47         0.03     18,023x

On this machine and workload, list search should grow with n,
while set lookup is usually average-case O(1). Measure your own
data: constants, hashing cost, memory, and access patterns matter.

This difference can matter when membership testing is a measured bottleneck, but it is not a substitute for profiling the real workload. The mechanism is a hash table, which you will examine next.

Understanding Big-O Notation

Now that you’ve witnessed this dramatic performance difference empirically, let’s understand the mathematical pattern behind it. Big-O notation describes how an algorithm’s runtime scales with input size - it’s the language computational scientists use to discuss whether an algorithm is feasible for large-scale simulations.

Big-O describes how runtime grows with input size—not the exact speed—so constants, memory layout, and access patterns still matter.

Asymptotic operation counts (ignoring constants and implementation details):

Complexity1,000 particles1,000,000 particlesExample
O(1)1 op1 opAverage-case hash-table lookup
O(log n)10 ops20 opsTree traversal
O(n)1,000 ops1,000,000 opsOne linear scan or reduction
O(n log n)10,000 ops20,000,000 opsSorting or some tree algorithms
O(n2\mathrm{n}^{2})1,000,000 ops1,000,000,000,000 opsAll-pairs comparison
Illustrative operation counts at n = 1,000,000:
  O(1):       1 operation           - one average-case hash lookup
  O(log n):   20 operations         - tree traversal
  O(n):       1,000,000 operations  - one scan or reduction
  O(n log n): 2e+07 operations - a sorting/tree-like cost
  O(n^2):      1,000,000,000,000 ops - all-pairs comparison

The table explains why a scaling question belongs beside a timing question. An all-pairs calculation can become infeasible at a particular problem size and hardware budget, but it is not made O(n log n) merely by putting data in a tree-shaped container. A tree algorithm needs an approximation policy; a spatial hash needs a cell size, occupancy distribution, adjacent-cell search, and final distance criterion.

4.2 Lists: Python’s Workhorse Sequence

Lists are Python’s most versatile data structure - useful for ordered measurements, records, or a changing collection. A list holds references to Python objects rather than a homogeneous raw-value buffer. That distinction matters when numerical array operations dominate, but it does not supply a universal speed comparison with NumPy.

A representation preview

Use a list when a changing, ordered collection is the clearest contract. When you need one dtype and repeated numerical operations across an entire grid or particle coordinate array, Chapter 7’s NumPy arrays provide a different model: an array stores same-type values in an explicit data buffer. The memory details, allocation behavior, and a careful measurement example appear in the Deep Dive, where they can be read when a real memory or performance question arises.

List Operations: Performance for Simulations

Different list operations have vastly different costs - critical knowledge when processing particle data or building spatial data structures.

n=    100: end=   0.07 us; beginning=   0.11 us
n= 10,000: end=   0.06 us; beginning=   3.70 us
n=100,000: end=   0.06 us; beginning=  39.01 us

This workload normally makes the beginning operation grow with n.
The timings are evidence about this Python build and workload, not a universal ratio.

Python lists overallocate so appending is amortized O(1): usually fast, with occasional resizing. The allocation pattern is implementation-dependent; the Deep Dive shows how to inspect it without treating one interpreter’s byte count as a language guarantee.

4.3 Tuples: The Power of Immutability

What if you need to guarantee that initial conditions or physical constants won’t change during your simulation? Enter tuples - Python’s immutable sequences. This isn’t a limitation - it’s protection against an entire category of bugs that plague scientific codes!

Understanding Immutability in Physics Simulations

Let’s see how immutability protects your simulations and enables powerful optimizations:

With mutable list:
  G was: 6.674e-08, now: 6.674e-05
  Constants CORRUPTED!

With immutable tuple:
  Caught bug immediately: 'tuple' object does not support item assignment

Lesson: Immutability turns silent corruption into immediate, loud errors!

Tuples as Dictionary Keys

A tuple whose contents are hashable can be a dictionary key. That is useful when an application’s declared inputs are naturally a discrete record—for example (temperature_index, density_index, composition_id). The deeper cache example below explains why this is an implementation choice, not a physical model or a promise that arbitrary floating-point rounding is safe.

4.4 The Mutable vs Immutable Distinction

Time for one of Python’s most important concepts for scientific computing! Understanding mutability is the key to avoiding mysterious bugs where your simulation state changes unexpectedly. This connects directly to Chapter 1’s defensive programming principles - catching errors early prevents corrupted results.

Python’s Reference Model in Physics Simulations

Python names are bindings to objects. For immutable values, rebinding one name does not change another name’s value. For a mutable container, two names can refer to the same object, so mutation through either name is visible through both. This distinction matters whenever a simulation claims to preserve an initial state or to return an independent next state:

With IMMUTABLE objects (safe for constants):
Initially: G=6.674e-08, backup=6.674e-08
Same object in memory? True
After change: G=6.674e-08, backup=6.674e-07
Original constant is safe!

==================================================
With MUTABLE objects (dangerous for state!):
Initially: same object? True
Original particles: [[999.0, 2.0, 3.0], [4.0, 5.0, 6.0]]
'Backup': [[999.0, 2.0, 3.0], [4.0, 5.0, 6.0]]
You just corrupted your simulation state!

Lesson: decide explicitly whether this function shares, copies, or rebuilds state.

The Classic Mutable Default Argument Bug

This bug is so dangerous that Python linters specifically check for it. It’s particularly insidious in iterative simulations:

Run 1:
  Energy history: [100, 95]

Run 2 (should be independent):
  Energy history: [100, 95, 200]
  Contains Run 1 data! Runs are coupled!
THE FIX: Use None sentinel pattern

Run 3 (intentional accumulation):
  Energy history: [100, 95]

Run 4 (properly independent):
  Energy history: [200]
  Runs are independent!

Shallow vs Deep Copies in Grid Simulations

This distinction is critical for grid-based simulations in computational fluid dynamics, stellar atmospheres, or any field-based calculation:

Simulating heat diffusion on a grid:
Initial grid: [20.0, 50.0, 20.0]

--- SHALLOW COPY (aliases inner arrays) ---
Original grid center: 35.0 degC
Next step grid center: 35.0 degC
Modified both grids! Simulation is corrupted!

--- DEEP COPY (independent arrays) ---
Original grid center: 50.0 degC
Next step grid center: 35.0 degC
Grids are independent for this update!

Memory visualization:
Shallow: grid -> [ref1, ref2, ref3] -> same inner arrays
Deep:    grid -> [ref1, ref2, ref3] -> independent arrays

4.5 Dictionaries: Average-Case O(1) Lookup for Physics

Now we explore one of computer science’s most useful inventions - the dictionary. In scientific Python, dictionaries are often useful for named properties, lookup tables, and caches. Their average-case lookup cost does not mean every numerical dataset should become a dictionary, nor that lookup is independent of key construction, memory, collisions, or the work after lookup.

Organizing Simulation Data with Dictionaries

Let’s see how dictionaries transform particle management in an N-body simulation:

Accessing particle_0002:
  Mass: 5.97e+27 g
  Type: planet

Particles by type: {'star': ['particle_0001'], 'planet': ['particle_0002']}
A mapping lets an application find a record from a known identifier.

Understanding Hash Tables for Physics Applications

Let’s demystify the average-case lookup mechanism used by dictionaries:

How hash tables accelerate physics lookups:
============================================================
Key                  -> Hash        -> Bucket
------------------------------------------------------------
T=1e+06, rho=1e-03   -> 7060515190775938164 ->  64
T=2e+06, rho=2e-03   -> -3756423604195419920 ->  20
T=5e+06, rho=1e-02   -> -5145451484737341997 ->  97
H_ionization         -> -7590979357125914885 ->  85
He_ionization        -> 217872519414005944 ->  44
opacity_table        -> 5260287046942931854 ->  54

The average-case lookup process:
1. Hash the key -> integer
2. Use it to find a candidate table location
3. Check the stored key and resolve collisions if needed

This illustrates a mapping lookup, not a physical table-interpolation method.

The membership benchmark in Section 4.1 is deliberately narrow: it compares one absent-item lookup. A dictionary can be a good representation when an application repeatedly maps known identifiers to records; the [Deep Dive] (#memory-caches-and-benchmarks) shows how to benchmark a broader workload and how cache keys encode a modeling decision.

4.6 Sets: Mathematical Operations for Particle Systems

Sets represent unique, hashable values and provide average-case O(1) membership testing plus useful set operations. They can track processed IDs or overlapping domain memberships; they do not themselves compute geometric neighbors.

Set Operations in Particle Dynamics

Overlapping IDs needing a consistency check: {'p004', 'p003', 'p007', 'p005'}
Interior particles (A): {'p001', 'p002'}
Total unique particles: 9
Particles in multiple domains: {'p004', 'p003', 'p007', 'p005'}

Sets make overlap bookkeeping explicit.
A production parallel code still needs ownership and communication rules.

Deep Dive: Memory, Caches, and Benchmarks

Now that we understand the core representations, this deeper path examines memory measurements, cache keys, and benchmark design. These details can guide a real optimization decision, but a local byte count or timing remains evidence about its own interpreter, input, and operation—not a universal library claim.

Memory Usage in Particle Systems

Let’s examine memory costs for different data structures in a particle simulation context:

Storing 10,000 particle IDs:
  List container: 85,176 bytes
  Set container:  524,504 bytes
  (Both containers refer to the same string objects.)
100 particles as list: 920 bytes
ID-to-position mapping: 4,688 bytes
The mapping supports lookup by known ID; it is not a spatial neighbor index.
Memory for 1,000 particles (ID, x, y, z):
-------------------------------------------------------
List of lists     :    8,856 bytes (container only!)
List of tuples    :    8,856 bytes (container only!)
Dictionary        :   36,952 bytes (container only!)
NumPy .nbytes     :   32,000 bytes (data buffer)
NumPy getsizeof   :   32,128 bytes (object; ownership-dependent)

Python-container sizes omit their contained objects.
For an owning NumPy array, getsizeof commonly includes its owned buffer;
.nbytes remains the direct statement of logical element storage.

Cache Efficiency in Grid Computations

Modern CPUs use several layers of memory. This experiment compares two traversal orders for one nested-list workload; interpreter overhead, CPU architecture, and the data representation all affect the result:

Simulating heat diffusion finite differences:

(Common in CFD and atmospheric modeling)
Processing 500x500 grid:
Row-first traversal:   23.3 ms
Column-first traversal: 22.7 ms
Ratio: 1.0x

Important caveat: each Python row is a separate list object.
This experiment does not predict a NumPy speedup or a production CFD runtime.

Allocation and cache decisions

Python lists overallocate so that repeated .append() is amortized O(1). The exact capacity sequence is an implementation detail, but observing allocation events can explain a local timing trace:

length  1: allocation changed by 32 bytes
length  5: allocation changed by 32 bytes
length  9: allocation changed by 64 bytes
length 17: allocation changed by 64 bytes
Exact byte counts and growth factors are interpreter-dependent.

Tuples can supply composite, hashable cache keys, but cache-key construction is a modeling decision. Raw floating-point values are appropriate only when the exact stored float values define the calculation. Rounding merely to increase cache hits can merge distinct cases; use grid indices or bins only when the model defines that discretization.

indices=(10, 5, 0); P=8.88e+11 dyne/cm^2; computed
indices=(10, 5, 0); P=8.88e+11 dyne/cm^2; cache_hit
indices=(11, 5, 0); P=9.77e+11 dyne/cm^2; computed
expensive calculations in this toy cache: 2

4.7 Choosing the Right Structure for Physics

After exploring all options, how do you choose? Here’s your decision framework for computational physics:

Performance Quick Reference for Physics

OperationListTupleDictSet
Access by indexO(1)O(1)N/AN/A
Search for value / membershipO(n)O(n)O(1)† by keyO(1)†
Add at end / new key or itemO(1)‡N/AO(1)†‡O(1)†‡
Remove a known value / key / itemO(n)N/AO(1)†O(1)†
Iteration orderinsertionfixed sequenceinsertion*unspecified

* Dicts maintain insertion order (Python 3.7+) † Average-case; worst-case can degrade with collisions ‡ Amortized; resize events can cost O(n)

Real Example: Combining Data Structures

Let’s see how different data structures work together in a simple particle tracking system:

Total active mass: 3.0e+30 g
Number of measurements: 3
Constants are protected: type(CONSTANTS) = tuple

Main Takeaways

You now have a way to reason about a representation before reaching for a method: name the access pattern, state whether mutation is allowed, and ask whether uniqueness or a key is part of the problem. A list preserves sequence, a tuple preserves an immutable record, a dictionary maps a known key to a value, and a set records unique hashable items.

The mutable-versus-immutable distinction is equally practical. An assignment can share a mutable object; a shallow copy shares its nested objects; a deep copy duplicates nested objects but may be the wrong cost or ownership choice for a large state. State whether a function mutates its input, returns a new state, or uses preallocated buffers, then test that contract.

Finally, complexity and timing are diagnostic tools. Average-case hash lookup can be valuable when known-ID lookup is the measured bottleneck, but it does not replace a spatial algorithm, a model approximation, or a workload-specific benchmark. When a calculation becomes slow, use this chapter to identify the question your representation must answer, then measure and redesign with the algorithm’s assumptions visible.

Once you can reason about control flow (Chapter 3) and data layout (this chapter), you’re ready to start abstracting your work: writing functions and modules that hide complexity while preserving correctness and performance. That’s the goal of Chapter 5.

Definitions

Aliasing: When two or more variables refer to the same object in memory, causing modifications through one to affect the others.

Amortized O(1): An operation that is usually O(1) but occasionally O(n), where the average over many operations remains O(1).

Big-O Notation: Mathematical notation describing how an algorithm’s runtime scales with input size, focusing on the dominant term.

Cache: Stored results reused to avoid repeating work. A Python memoization cache is distinct from the CPU’s hardware cache.

Data Structure: A way of organizing data in computer memory to enable efficient access and modification.

Deep Copy: Creating a recursively independent object graph. It is useful only when that ownership and copying cost match the intended state contract.

Dictionary: A mutable mapping type storing key-value pairs with average-case O(1) lookup time using hash tables. Worst-case O(n) with hash collisions.

Hash Function: A function mapping data of arbitrary size to fixed-size values, enabling fast lookups.

Hash Table: The underlying implementation for dictionaries and sets, enabling average-case O(1) lookups. Worst-case O(n) when many keys collide.

Immutable: Objects whose state cannot be modified after creation (tuples, strings, numbers).

List: Python’s built-in mutable sequence type that stores references to objects in order.

Memoization: Caching technique storing function results to avoid recalculating for the same inputs.

Mutable: Objects whose state can be modified after creation (lists, dictionaries, sets).

Named Tuple: A tuple subclass allowing element access by name as well as index.

O(1) - Constant Time: An operation whose asymptotic work does not grow with input size; hash-table O(1) claims are usually average-case.

O(n) - Linear Time: An operation whose runtime grows proportionally with input size.

O(n2\mathrm{n}^{2}) - Quadratic Time: An operation whose runtime grows with the square of input size.

O(log n) - Logarithmic Time: An operation whose runtime grows logarithmically with input size.

O(n log n): Common complexity for efficient sorting algorithms and tree-based operations.

Reference / binding: A name can refer to an object; two names may refer to the same mutable object. Python does not promise that this corresponds to a user-visible memory address.

Set: A mutable collection of unique, unordered elements with average-case O(1) membership testing.

Shallow Copy: Creating a new container with references to the same contained objects.

Spatial Hashing: Organizing particles by spatial region so a query can first find candidate cells by key. Neighbor-query cost also depends on cell occupancy, adjacent cells, and distance checks.

Tuple: An immutable sequence type that cannot be changed after creation.

Key Takeaways

✓ Choose a representation from its ordering, mutability, key, and uniqueness contract.

✓ List membership is O(n); dictionary and set lookup are average-case O(1) for hashable keys or items.

✓ Assignment and shallow copying can share nested mutable state; document whether a function mutates, copies, or rebuilds it.

✓ A tuple can protect an immutable record and can serve as a hashable composite key when its contents are hashable.

✓ A list/set/dictionary timing is evidence about one workload. It does not establish a production speedup or replace a spatial algorithm.

✓ sys.getsizeof() needs an ownership-aware interpretation; measure contained objects and array buffers deliberately.

✓ Caches, spatial indices, and specialized numerical arrays are additional designs with their own assumptions and memory costs.

Reference: Structures, Operations, and Modules

This reference section catalogs all Python modules, functions, and methods introduced in this chapter. Keep this as a quick lookup guide for your physics simulations.

Standard Library Modules

time module - High-resolution timing for performance measurement

import time

sys module - System-specific parameters (expanded from Chapter 1)

import sys

copy module - Create object copies with control over depth

import copy

random module - Generate random numbers for Monte Carlo

import random

math module - Mathematical functions (review from Chapter 2)

import math

Collections Module

collections module - Specialized container datatypes

from collections import OrderedDict, Counter, defaultdict, deque, namedtuple

OrderedDict - Dictionary that remembers insertion order

Counter - Dictionary subclass for counting hashable objects

defaultdict - Dictionary with default value factory

deque - Double-ended queue with O(1) operations at both ends

namedtuple - Tuple subclass with named fields

Particle = namedtuple('Particle', ['id', 'mass', 'x', 'y', 'z'])
p = Particle(1, 1.0e30, 0, 0, 0)
print(p.mass)  # Access by name

Functools Module

functools module - Higher-order functions and operations

from functools import lru_cache

@lru_cache(maxsize=128) - Decorator for automatic memoization

@lru_cache(maxsize=1000)
def expensive_calculation(T, rho):
    return complex_physics_calculation(T, rho)

Built-in Functions for Data Structures

Type Checking

Container Operations

Copying

Data Structure Methods Summary

List Methods (Expanded)

Dictionary Methods (Expanded)

Set Methods (Expanded)

Reference tables

Data Structure Operations

OperationListTupleDictSet
Create empty[](){}set()
Create with items[1,2,3](1,2,3){'a':1}{1,2,3}
Add item.append(x)N/Ad[k]=v.add(x)
Remove item.remove(x)N/Adel d[k].remove(x)
Check membershipx in listx in tuplek in dictx in set
Get by indexlist[i]tuple[i]N/AN/A

Performance Quick Reference

OperationListTupleDictSetDeque
Access by indexO(1)O(1)N/AN/AO(n)
Search for value / membershipO(n)O(n)O(1)*‡O(1)‡O(n)
Add to end / new itemO(1)†N/AO(1)†‡O(1)†‡O(1)
Add to beginningO(n)N/AN/AN/AO(1)
Remove from endO(1)N/AN/AN/AO(1)
Remove from beginningO(n)N/AN/AN/AO(1)
Remove specificO(n)N/AO(1)‡O(1)‡O(n)

* Dict searches by key, not value † Amortized - occasionally O(n) during resize ‡ Average-case; worst-case can degrade with collisions

The † and ‡ markers qualify every marked O(1) entry: they describe the named container operation, not the cost of a whole scientific workload.

Common Methods for Physics

StructureMethodPhysics Use Case
list.append(x)Add new particle
list.extend(iter)Merge particle lists
dict.get(k, default)Safe parameter lookup
dict@lru_cacheAutomatic memoization
set.union(other)Combine particle domains
set.intersection(other)Find overlapping IDs or records
deque.appendleft()Rolling window or pending-work queue
OrderedDict.move_to_end()LRU cache management

When to Use Each Structure

Physics TaskBest ChoiceWhy
Particle positionsList, then a typed array when array operations dominateOrdered records; explicit numerical representation when warranted
Particle lookup by IDDictionaryAverage-case lookup by known key
Active particlesSetAverage-case membership, uniqueness
Physical constantsTuple/NamedTupleImmutable, safe
EOS cacheDictionary/LRU cacheReuse results for a declared discrete-input contract
Collision candidatesSetTrack unique candidate IDs; geometry remains separate
Time seriesListOrdered, allows duplicates
Spatial gridMapping from cell ID to recordsAverage-case cell lookup plus stated neighbor-query policy
Pending boundary-work queueDequeFast operations at both ends
Particle countsCounterAutomatic histogram creation

Recap and next connection

With data structures mastered, Chapter 5 will explore functions and modules—how to make data representations and scientific assumptions explicit, reusable, and testable. Begin with its core function-contract example; return to the advanced function patterns when a real interface needs them.

Footnotes
  1. Lions, J.L., et al. (1996). “Ariane 5 Flight 501 Failure Report.” ESA/CNES.

  2. Jones, M.B. (1997). “What Really Happened on Mars.” Microsoft Research.

  3. U.S. Securities and Exchange Commission (2013). Release No. 70694.

  4. Paxton et al. (2011). “Modules for Experiments in Stellar Astrophysics (MESA).” ApJS, 192, 3.