Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5557dde
chore: update GitHub Actions to resolve dependabot warnings
corneliusroemer Oct 14, 2025
b0fb88d
fix: resolve CI failures with artifacts and macOS runner
corneliusroemer Oct 14, 2025
f33f897
chore: drop unsupported Python versions from CI
corneliusroemer Oct 14, 2025
0145010
fix: resolve build failures and add cross-platform support
corneliusroemer Oct 14, 2025
c3981f5
fix: resolve CI build failures
corneliusroemer Oct 14, 2025
cde07cb
fix: add C++11 standard and fix test runner
corneliusroemer Oct 14, 2025
d5d3b9f
fix: commit SWIG wrapper and remove CI generation
corneliusroemer Oct 14, 2025
e8db7c1
fix: set macOS deployment target to 13.0 for GSL compatibility
corneliusroemer Oct 14, 2025
40e1ad6
fix: pin numpy<2 in tests and unify test commands
corneliusroemer Oct 14, 2025
b116c00
fix: correct SWIG_Python_AppendOutput is_void parameter
corneliusroemer Oct 14, 2025
9302301
ci: upgrade to macOS 15 for both Intel and ARM64
corneliusroemer Oct 14, 2025
a32bd21
fix: update macOS deployment target to 14.0 for macOS 15 runners
corneliusroemer Oct 14, 2025
0e413d1
fix: skip cross-architecture wheel testing on macOS
corneliusroemer Oct 14, 2025
fff0d9c
fix: use macos-14 (ARM64) and macos-15-intel with deployment target 14.0
corneliusroemer Oct 14, 2025
450eb08
fix: plug memory leaks in haploid_highd destructor
corneliusroemer Oct 14, 2025
4c28d2a
fix: prevent integer overflow in provide_at_least()
corneliusroemer Oct 14, 2025
a78f520
fix: Python 3 compatibility in examples
corneliusroemer Oct 14, 2025
03c0942
ci: run examples serially without parallel
corneliusroemer Oct 14, 2025
99510e4
ci: build only native architecture on each macOS runner
corneliusroemer Oct 14, 2025
21e4793
Delete test_ffpopsim.py
corneliusroemer Oct 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 26 additions & 13 deletions .github/workflows/build_and_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:

steps:
- name: "Checkout code"
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 1
submodules: true
Expand All @@ -53,27 +53,36 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ ubuntu-24.04, macOS-11 ]
python-version: [ 36, 37, 38, 39, 310, 311 ]
os: [ ubuntu-24.04, macos-14, macos-15-intel ]
python-version: [ 310, 311 ]

steps:
- name: "Checkout code"
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 1
submodules: true

- name: "Build wheel (${{ matrix.os }}, py${{ matrix.python-version }})"
uses: pypa/cibuildwheel@v2.11.1
uses: pypa/cibuildwheel@v2.21.3
with:
config-file: cibuildwheel.toml
env:
CIBW_BUILD: "cp${{ matrix.python-version}}-*"
# Override deployment target to 14.0 for all macOS runners
MACOSX_DEPLOYMENT_TARGET: "14.0"
# Build only native architecture on each runner (no cross-compilation)
CIBW_ARCHS_MACOS: >-
${{
matrix.os == 'macos-14' && 'arm64' ||
matrix.os == 'macos-15-intel' && 'x86_64' ||
'auto'
}}

- name: "Upload build artifacts (${{ matrix.os }}, py${{ matrix.python-version }})"
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: dist
name: dist-${{ matrix.os }}-py${{ matrix.python-version }}
path: ./wheelhouse/*.whl

make_sdist:
Expand All @@ -83,7 +92,7 @@ jobs:

steps:
- name: "Checkout code"
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 1
submodules: true
Expand All @@ -94,9 +103,9 @@ jobs:
pipx run build --sdist

- name: "Upload build artifacts"
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: "dist"
name: "dist-sdist"
path: "dist/*.tar.gz"

publish_to_pypi:
Expand All @@ -113,10 +122,14 @@ jobs:

steps:
- name: "Download build artifacts"
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: "dist"
path: "dist"
path: "artifacts"

- name: "Collect all artifacts into dist folder"
run: |
mkdir -p dist
find artifacts -type f \( -name '*.whl' -o -name '*.tar.gz' \) -exec cp {} dist/ \;

- name: "Publish to PyPI"
if: startsWith(github.ref, 'refs/tags')
Expand Down
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@ tests/FFPopSim.py
tests/FFPopSim.pyc
tests/_FFPopSim.so

# SWIG generated files (they are kept in the static-gsl branch)
src/python/FFPopSim.py
src/python/FFPopSim_wrap.cpp
# SWIG generated Python module (FFPopSim.py is kept in repo)
# Wrapper is now committed to ensure consistent SWIG 4.3.1 across platforms

# Compiled Object files
*.slo
Expand Down
20 changes: 12 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
# Please set your Python 3 executable if you want to build the Python
# bindings. If you are only interested in the C++ part of the library,
# comment out the following line
PYTHON := python3
PYTHON := micromamba run -n ffpopsim11 python

# Note: please look in 'setup.py' if you are building the Python extension!
# You can call distutils with 'setup.py' directly if you prefer. The
Expand All @@ -73,11 +73,19 @@ OPTIMIZATION_LEVEL := O2

# Please use the following variable for additional flags to the C++ compiler,
# such as include folders (e.g. -I/opt/local/include)
CXXFLAGS = -c -Wall -$(OPTIMIZATION_LEVEL) -fPIC
GSL_CFLAGS := $(shell gsl-config --cflags 2>/dev/null)
# Try to find Boost in common locations (macOS Homebrew, MacPorts, or standard Linux paths)
BOOST_INCLUDE := $(shell \
if [ -d /opt/homebrew/opt/boost/include ]; then echo "-I/opt/homebrew/opt/boost/include"; \
elif [ -d /usr/local/opt/boost/include ]; then echo "-I/usr/local/opt/boost/include"; \
elif [ -d /opt/local/include/boost ]; then echo "-I/opt/local/include"; \
fi)
CXXFLAGS = -c -Wall -$(OPTIMIZATION_LEVEL) -fPIC -std=c++11 $(GSL_CFLAGS) $(BOOST_INCLUDE)

# Please use the following variable for additional flags to the linker, such
# as library folders for GSL (e.g. -L/opt/local/lib)
LDFLAGS = -$(OPTIMIZATION_LEVEL)
GSL_LDFLAGS := $(shell gsl-config --libs 2>/dev/null | sed 's/-lgsl -lgslcblas//')
LDFLAGS = -$(OPTIMIZATION_LEVEL) $(GSL_LDFLAGS)

# Additional options used to regenerate the SWIG files or to rebuild the docs.

Expand Down Expand Up @@ -263,11 +271,7 @@ $(TESTSDIR)/$(TESTS_OBJECT_GENEALOGY): $(TESTSDIR)/$(TESTS_SOURCE_GENEALOGY)
$(CXX) $(TESTS_CXXFLAGS) -c $(@:.o=.cpp) -o $@

run-tests:
cd "tests" && ./highd
cd "tests" && ./hivpopulation
cd "tests" && ./lowd
cd "tests" && ./recombination_lowd
cd "tests" && ./test_genealogy
cd tests && ./highd && ./hivpopulation && ./lowd && ./recombination_lowd && ./test_genealogy

clean-tests:
cd $(TESTSDIR); rm -rf *.o $(TESTS_LOWD) $(TESTS_HIGHD) $(TESTS_GENEALOGY) $(TESTS_LOWD_REC) $(TESTS_HIVPOP)
Expand Down
47 changes: 29 additions & 18 deletions cibuildwheel.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,46 +11,57 @@ skip = [
"pp*",
]

macos.archs = ["x86_64", "arm64"]
# Architecture is set per-runner in the workflow file to avoid cross-compilation
# GSL libraries from Homebrew require macOS 14.0 minimum
macos.environment = {MACOSX_DEPLOYMENT_TARGET = "14.0"}

build-verbosity = 1

linux.before-build = [
"yum makecache fast",
"yum install -y boost-devel gsl-devel >/dev/null",
"make swig",
]

macos.before-build = [
"brew install boost gsl",
"make swig",
]


test-skip = [
"*aarch64*",
"*arm64*",
"*ppc64*",
"*s390x*",
]


linux.before-test = [
"yum install -y parallel >/dev/null",
"pip install biopython matplotlib numpy pandas pygments scipy"
"pip install 'biopython' 'matplotlib' 'numpy<2' 'pandas' 'pygments' 'scipy'"
]

# Run examples serially (one at a time) without parallel to avoid memory issues in CI
linux.test-command = [
"cd {project} && bash ./run-examples --skip-slow"
"""cd {project}/examples && for f in *.py; do
case "$f" in
benefits_of_sex.py|genealogies_with_selection.py|measuring_fixation_probabilities.py|multi_sample_adaptive.py|mutation_selection_balance_highd.py|neutral_LD_highd.py|speed_highd.py|speed_lowd.py)
echo "Skipping slow example: $f"
;;
*)
echo "Testing $f..."
MPLBACKEND=Agg python3 "$f" || exit 1
;;
esac
done"""
]


macos.before-test = [
"brew install parallel >/dev/null",
"pip install biopython matplotlib numpy pandas pygments scipy"
"pip install 'biopython' 'matplotlib' 'numpy<2' 'pandas' 'pygments' 'scipy'"
]

# Tests are very slow on mac, so only do basics
# Run same tests as Linux for consistency - serially without parallel
macos.test-command = [
"cd {project} && python examples/example.py"
"""cd {project}/examples && for f in *.py; do
case "$f" in
benefits_of_sex.py|genealogies_with_selection.py|measuring_fixation_probabilities.py|multi_sample_adaptive.py|mutation_selection_balance_highd.py|neutral_LD_highd.py|speed_highd.py|speed_lowd.py)
echo "Skipping slow example: $f"
;;
*)
echo "Testing $f..."
MPLBACKEND=Agg python3 "$f" || exit 1
;;
esac
done"""
]
4 changes: 2 additions & 2 deletions examples/genealogies_with_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@
#set the effect sizes of the mutations that are injected (the same at each site in this case)
pop.set_fitness_additive(np.ones(L)*s)

#track the genealogy at a central locus L/2 (which one doesn't matter in the asexual case)
pop.track_locus_genealogy([L/2])
#track the genealogy at a central locus L//2 (which one doesn't matter in the asexual case)
pop.track_locus_genealogy([L//2])

#initialize the populations
pop.set_wildtype(pop.carrying_capacity)
Expand Down
2 changes: 1 addition & 1 deletion examples/speed_highd.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@

pop.set_wildtype(N) # set a wildtype population of size N

pop.evolve(1.0 / (L * (mu + r))) # evolve until equilibrium
pop.evolve(int(1.0 / (L * (mu + r)))) # evolve until equilibrium

# run for G generations to measure execution time
t1=time.time()
Expand Down
31 changes: 30 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# vim: fdm=indent

import sys
import subprocess
import os

# Python 3.6 compatibility: Python 3.6 has no compatible version of setuptools that supports
# PEP-517 (the `pyproject.toml`-based config), so we use a shim package `ppsetuptools` instead.
Expand All @@ -12,6 +14,31 @@

import numpy as np

# Get GSL include paths
def get_gsl_include():
try:
output = subprocess.check_output(['gsl-config', '--cflags'], stderr=subprocess.DEVNULL)
cflags = output.decode().strip().split()
return [flag[2:] for flag in cflags if flag.startswith('-I')]
except:
return []

# Get Boost include paths
def get_boost_include():
for path in ['/opt/homebrew/opt/boost/include', '/usr/local/opt/boost/include', '/opt/local/include']:
if os.path.isdir(path):
return [path]
return []

# Get GSL library paths
def get_gsl_lib_dirs():
try:
output = subprocess.check_output(['gsl-config', '--libs'], stderr=subprocess.DEVNULL)
ldflags = output.decode().strip().split()
return [flag[2:] for flag in ldflags if flag.startswith('-L')]
except:
return []

setup_args = dict(
py_modules=["FFPopSim"],
package_dir={ '': 'src/python' },
Expand All @@ -29,8 +56,10 @@
'src/hypercube_lowd.cpp',
'src/hypercube_highd.cpp',
],
include_dirs=[np.get_include()],
include_dirs=[np.get_include()] + get_gsl_include() + get_boost_include(),
library_dirs=get_gsl_lib_dirs(),
libraries=['gsl', 'gslcblas'],
extra_compile_args=['-std=c++11'],
py_limited_api = True
)
]
Expand Down
13 changes: 13 additions & 0 deletions src/haploid_highd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,18 @@ int haploid_highd::free_mem() {
cerr <<"haploid_highd::free_mem(): No memory allocated!\n";
return HP_BADARG;
} else {
delete [] genome;
delete [] crossovers;
delete [] allele_frequencies;
delete [] gamete_allele_frequencies;
delete [] trait;
delete [] trait_stat;
for (int t = 0; t < number_of_traits; t++){
delete [] trait_covariance[t];
}
delete [] trait_covariance;
delete [] trait_weights;
gsl_rng_free(evo_generator);
mem = false;
return 0;
}
Expand All @@ -203,6 +212,10 @@ int haploid_highd::free_mem() {
int haploid_highd::provide_at_least(int n) {
//calculate the number of clones that need to be newly allocated. Allow for some slack
//to avoid calling this too often
// BUG FIX: Check if we already have enough clones to avoid unsigned arithmetic underflow
if (available_clones.size() >= n) {
return 0; // We already have enough clones available
}
int needed_gts = n - available_clones.size() + 100 + 0.1 * population.size();

//allocate at the necessary memory
Expand Down
Loading