Scientific Programming
Best practices for research software and reproducible computation.
Project Structure
project/
├── README.md # Project overview, how to reproduce
├── LICENSE # MIT, Apache 2.0, or GPL
├── requirements.txt # or environment.yml (conda)
├── setup.py / pyproject.toml
├── data/
│ ├── raw/ # Never modify raw data
│ ├── processed/ # Cleaned/transformed data
│ └── external/ # Third-party data
├── src/ or scripts/
│ ├── data_processing.py
│ ├── analysis.py
│ ├── models.py
│ └── visualization.py
├── notebooks/ # Exploratory analysis
│ ├── 01_eda.ipynb
│ ├── 02_modeling.ipynb
│ └── 03_figures.ipynb
├── results/
│ ├── figures/
│ └── tables/
├── tests/
└── docs/
Reproducibility Checklist
Environment: Pin all dependencies with versions
pip freeze > requirements.txt
# or conda
conda env export > environment.yml
Random seeds: Set and document all random seeds
import numpy as np
import random
SEED = 42
np.random.seed(SEED)
random.seed(SEED)
# torch.manual_seed(SEED)
# tf.random.set_seed(SEED)
Data versioning: Use DVC or git-lfs for large data
dvc init
dvc add data/raw/dataset.csv
git add data/raw/dataset.csv.dvc
Configuration: Separate config from code
# config.yaml
# experiment:
# learning_rate: 0.001
# batch_size: 32
# epochs: 100
import yaml
with open('config.yaml') as f:
config = yaml.safe_load(f)
Logging: Record all experiments
import logging
logging.basicConfig(level=logging.INFO,
format='%(asctime)s %(levelname)s: %(message)s',
filename='experiment.log')
Parallel Computing
# Multiprocessing (CPU-bound)
from multiprocessing import Pool
import numpy as np
def process_chunk(data):
return heavy_computation(data)
with Pool(processes=8) as pool:
results = pool.map(process_chunk, data_chunks)
# Concurrent futures (simpler API)
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
with ProcessPoolExecutor(max_workers=8) as executor:
results = list(executor.map(process_func, items))
# For I/O-bound tasks (API calls, file reading)
with ThreadPoolExecutor(max_workers=20) as executor:
results = list(executor.map(fetch_data, urls))
Performance Optimization
# Profiling
import cProfile
cProfile.run('my_function()', sort='cumulative')
# Line profiling
# pip install line_profiler
# @profile decorator, then: kernprof -l -v script.py
# NumPy vectorization (avoid loops)
# Bad:
result = [x**2 + 2*x + 1 for x in data]
# Good:
result = data**2 + 2*data + 1
# Memory profiling
# pip install memory_profiler
# @profile decorator, then: python -m memory_profiler script.py
Data Management
FAIR Principles
- Findable: Persistent identifiers (DOI), rich metadata
- Accessible: Open protocols, authentication when needed
- Interoperable: Standard formats (CSV, JSON, HDF5, NetCDF)
- Reusable: Clear license, provenance, community standards
File Formats for Science
| Format |
Best For |
Size |
Speed |
| CSV |
Small tabular, universal |
Large |
Slow |
| Parquet |
Large tabular, columnar |
Small |
Fast |
| HDF5 |
Multidimensional arrays |
Small |
Fast |
| NetCDF |
Climate/geospatial |
Small |
Fast |
| FITS |
Astronomy |
Medium |
Fast |
| Feather |
DataFrame interchange |
Small |
Very fast |
# Parquet (recommended for large datasets)
df.to_parquet('data.parquet', compression='snappy')
df = pd.read_parquet('data.parquet')
# HDF5 (for arrays)
import h5py
with h5py.File('data.h5', 'w') as f:
f.create_dataset('experiment1', data=array)
Testing Scientific Code
import numpy as np
import pytest
def test_conservation_law():
"""Physical quantities should be conserved"""
initial_energy = compute_energy(initial_state)
final_energy = compute_energy(simulate(initial_state))
np.testing.assert_allclose(initial_energy, final_energy, rtol=1e-6)
def test_known_solution():
"""Compare against analytical solution"""
numerical = solve_numerically(params)
analytical = analytical_solution(params)
np.testing.assert_allclose(numerical, analytical, atol=1e-4)
def test_symmetry():
"""Result should be symmetric under transformation"""
result1 = compute(data)
result2 = compute(transform(data))
np.testing.assert_array_equal(result1, result2)
Tips
- Raw data is sacred — never modify it, only create processed copies
- Use version control (git) from day one
- Write README before writing code
- Automate the full pipeline (Makefile or Snakemake)
- Document assumptions and decisions in code comments
- Use type hints for clarity in scientific code
- Publish code alongside papers (GitHub + Zenodo for DOI)
1---2name: code-science3description: Scientific programming best practices including reproducible research, computational notebooks, version control for research, data management, HPC/parallel computing, and research software engineering. Use when user needs help with research code organization, reproducibility, scientific Python/R workflows, or computational infrastructure. Triggers on "reproducible research", "research code", "scientific computing", "HPC", "parallel computing", "Jupyter", "notebook", "data management plan", "research software", "code review for science".4---5
6# Scientific Programming
7
8Best practices for research software and reproducible computation.
9
10## Project Structure
11
12```
13project/
14├── README.md # Project overview, how to reproduce
15├── LICENSE # MIT, Apache 2.0, or GPL
16├── requirements.txt # or environment.yml (conda)
17├── setup.py / pyproject.toml
18├── data/
19│ ├── raw/ # Never modify raw data
20│ ├── processed/ # Cleaned/transformed data
21│ └── external/ # Third-party data
22├── src/ or scripts/
23│ ├── data_processing.py
24│ ├── analysis.py
25│ ├── models.py
26│ └── visualization.py
27├── notebooks/ # Exploratory analysis
28│ ├── 01_eda.ipynb
29│ ├── 02_modeling.ipynb
30│ └── 03_figures.ipynb
31├── results/
32│ ├── figures/
33│ └── tables/
34├── tests/
35└── docs/
36```
37
38## Reproducibility Checklist
39
401. **Environment**: Pin all dependencies with versions
41 ```bash
42 pip freeze > requirements.txt
43 # or conda
44 conda env export > environment.yml
45 ```
46
472. **Random seeds**: Set and document all random seeds
48 ```python
49 import numpy as np
50 import random
51 SEED = 42
52 np.random.seed(SEED)
53 random.seed(SEED)
54 # torch.manual_seed(SEED)
55 # tf.random.set_seed(SEED)
56 ```
57
583. **Data versioning**: Use DVC or git-lfs for large data
59 ```bash
60 dvc init
61 dvc add data/raw/dataset.csv
62 git add data/raw/dataset.csv.dvc
63 ```
64
654. **Configuration**: Separate config from code
66 ```python
67 # config.yaml
68 # experiment:
69 # learning_rate: 0.001
70 # batch_size: 32
71 # epochs: 100
72 import yaml
73 with open('config.yaml') as f:
74 config = yaml.safe_load(f)
75 ```
76
775. **Logging**: Record all experiments
78 ```python
79 import logging
80 logging.basicConfig(level=logging.INFO,
81 format='%(asctime)s %(levelname)s: %(message)s',
82 filename='experiment.log')
83 ```
84
85## Parallel Computing
86
87```python
88# Multiprocessing (CPU-bound)
89from multiprocessing import Pool
90import numpy as np
91
92def process_chunk(data):
93 return heavy_computation(data)
94
95with Pool(processes=8) as pool:
96 results = pool.map(process_chunk, data_chunks)
97
98# Concurrent futures (simpler API)
99from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
100
101with ProcessPoolExecutor(max_workers=8) as executor:
102 results = list(executor.map(process_func, items))
103
104# For I/O-bound tasks (API calls, file reading)
105with ThreadPoolExecutor(max_workers=20) as executor:
106 results = list(executor.map(fetch_data, urls))
107```
108
109## Performance Optimization
110
111```python
112# Profiling
113import cProfile
114cProfile.run('my_function()', sort='cumulative')
115
116# Line profiling
117# pip install line_profiler
118# @profile decorator, then: kernprof -l -v script.py
119
120# NumPy vectorization (avoid loops)
121# Bad:
122result = [x**2 + 2*x + 1 for x in data]
123# Good:
124result = data**2 + 2*data + 1
125
126# Memory profiling
127# pip install memory_profiler
128# @profile decorator, then: python -m memory_profiler script.py
129```
130
131## Data Management
132
133### FAIR Principles
134- **Findable**: Persistent identifiers (DOI), rich metadata
135- **Accessible**: Open protocols, authentication when needed
136- **Interoperable**: Standard formats (CSV, JSON, HDF5, NetCDF)
137- **Reusable**: Clear license, provenance, community standards
138
139### File Formats for Science
140| Format | Best For | Size | Speed |
141|--------|----------|------|-------|
142| CSV | Small tabular, universal | Large | Slow |
143| Parquet | Large tabular, columnar | Small | Fast |
144| HDF5 | Multidimensional arrays | Small | Fast |
145| NetCDF | Climate/geospatial | Small | Fast |
146| FITS | Astronomy | Medium | Fast |
147| Feather | DataFrame interchange | Small | Very fast |
148
149```python
150# Parquet (recommended for large datasets)
151df.to_parquet('data.parquet', compression='snappy')
152df = pd.read_parquet('data.parquet')
153
154# HDF5 (for arrays)
155import h5py
156with h5py.File('data.h5', 'w') as f:
157 f.create_dataset('experiment1', data=array)
158```
159
160## Testing Scientific Code
161
162```python
163import numpy as np
164import pytest
165
166def test_conservation_law():
167 """Physical quantities should be conserved"""
168 initial_energy = compute_energy(initial_state)
169 final_energy = compute_energy(simulate(initial_state))
170 np.testing.assert_allclose(initial_energy, final_energy, rtol=1e-6)
171
172def test_known_solution():
173 """Compare against analytical solution"""
174 numerical = solve_numerically(params)
175 analytical = analytical_solution(params)
176 np.testing.assert_allclose(numerical, analytical, atol=1e-4)
177
178def test_symmetry():
179 """Result should be symmetric under transformation"""
180 result1 = compute(data)
181 result2 = compute(transform(data))
182 np.testing.assert_array_equal(result1, result2)
183```
184
185## Tips
186- Raw data is sacred — never modify it, only create processed copies
187- Use version control (git) from day one
188- Write README before writing code
189- Automate the full pipeline (Makefile or Snakemake)
190- Document assumptions and decisions in code comments
191- Use type hints for clarity in scientific code
192- Publish code alongside papers (GitHub + Zenodo for DOI)