Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ jobs:
build:
name: ${{ matrix.os }} ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}
defaults:
run:
# bash also on windows, e.g. for the dist/*.tar.gz glob
shell: bash
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]
steps:
- uses: actions/checkout@v7
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ jobs:
- ubuntu-24.04-arm # linux aarch64
- macos-15-intel # macos x86_64
- macos-latest # macos arm64
# tests are failing for now on windows.
# - windows-latest
- windows-latest # windows x86_64
- windows-11-arm # windows arm64

steps:
- uses: actions/checkout@v7
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ changes are released with the following version.
([#29](https://github.com/DessimozLab/pyopa/pull/29)).
- Wheels for Linux aarch64 and for Python 3.14 and 3.15
([#27](https://github.com/DessimozLab/pyopa/pull/27)).
- Wheels for Windows x86_64 and arm64 (Python 3.11 or newer on arm64, as
numpy has no Windows arm64 wheels for Python 3.10); Windows is tested in
CI ([#33](https://github.com/DessimozLab/pyopa/pull/33)).

### Changed
- Requires Python 3.10 or newer; no more wheels for Python 3.6 to 3.9.
Expand Down Expand Up @@ -53,6 +56,9 @@ changes are released with the following version.
matrices while `estimate_many_pam` is running; using freed matrices
raises a `RuntimeError` instead of crashing
([#28](https://github.com/DessimozLab/pyopa/pull/28)).
- `align_double` truncated pointers on 64-bit Windows, where `long` is
32 bits ([#33](https://github.com/DessimozLab/pyopa/pull/33)).
- Compiler warnings about implicit conversions with MSVC ([#33](https://github.com/DessimozLab/pyopa/pull/33)).

## 0.8.6 - 2025-04-24

Expand Down
6 changes: 3 additions & 3 deletions c_source/DynProgr_sse_byte.c
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ EXPORT double swps3_alignmentByteSSE_lin(ProfileByte * query, const char * db,

__m128i vMinimums = _mm_set1_epi32(0);

__m128i vDelFixed = _mm_set1_epi8(-options->gapOpen);
__m128i vDelFixed = _mm_set1_epi8((char) -options->gapOpen);
__m128i vBias = _mm_set1_epi8(query->bias);

__m128i vMaxScore = vMinimums; /* vMaxScore = [0,0] */
Expand Down Expand Up @@ -265,8 +265,8 @@ EXPORT double swps3_alignmentByteSSE(ProfileByte * query, const char * db,

__m128i vMinimums = _mm_set1_epi32(0);

__m128i vDelIncr = _mm_set1_epi8(-options->gapExt);
__m128i vDelFixed = _mm_set1_epi8(-options->gapOpen);
__m128i vDelIncr = _mm_set1_epi8((char) -options->gapExt);
__m128i vDelFixed = _mm_set1_epi8((char) -options->gapOpen);
__m128i vBias = _mm_set1_epi8(query->bias);

__m128i vMaxScore = vMinimums; /* vMaxScore = [0,0] */
Expand Down
4 changes: 3 additions & 1 deletion c_source/DynProgr_sse_double.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@

/* According to http://stackoverflow.com/questions/1919183/how-to-allocate-and-free-aligned-memory-in-c */

#include <stdint.h>
#include "Python_extension.h"
#include "DynProgr_sse_double.h"

void *aligned_malloc(int align, int size) {
char *mem = malloc(size + align + sizeof(char*));
char **ptr = (char**) ((long) (mem + align + sizeof(char*)) & ~(align - 1));
/* uintptr_t, as long is only 32 bits on 64-bit Windows */
char **ptr = (char**) ((uintptr_t) (mem + align + sizeof(char*)) & ~(uintptr_t) (align - 1));
ptr[-1] = mem;
return ptr;
}
Expand Down
6 changes: 3 additions & 3 deletions c_source/DynProgr_sse_short.c
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ EXPORT double swps3_alignmentShortSSE_lin(ProfileShort * query, const char * db,

__m128i vMinimums = _mm_set1_epi16(0x8000);

__m128i vDelFixed = _mm_set1_epi16(options->gapOpen);
__m128i vDelFixed = _mm_set1_epi16((short) options->gapOpen);
__m128i vBias = _mm_set1_epi16(query->bias);

__m128i vMaxScore = vMinimums; /* vMaxScore = [0,0] */
Expand Down Expand Up @@ -254,8 +254,8 @@ EXPORT double swps3_alignmentShortSSE(ProfileShort * query, const char * db,

__m128i vMinimums = _mm_set1_epi16(0x8000);

__m128i vDelIncr = _mm_set1_epi16(options->gapExt);
__m128i vDelFixed = _mm_set1_epi16(options->gapOpen);
__m128i vDelIncr = _mm_set1_epi16((short) options->gapExt);
__m128i vDelFixed = _mm_set1_epi16((short) options->gapOpen);
__m128i vBias = _mm_set1_epi16(query->bias);

__m128i vMaxScore = vMinimums; /* vMaxScore = [0,0] */
Expand Down
2 changes: 1 addition & 1 deletion c_source/EstimatePam.c
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ DayMatrix* createDayMatrices(double* gapOpen, double* gapExt,
#ifdef PY_DEBUG
printf("Creating day matrix:\n");
printf("FixedDel = %f\nIncDel = %f\nPamNumber = %f\n", gapOpen[i], gapExt[i], pamDistances[i]);
printf("Simi at %lx:\n", (long)matrix_pointers[i]);
printf("Simi at %p:\n", (void*) matrix_pointers[i]);
for(k = 0; k < MATRIX_DIM; ++k) {
printf("\t[");
for(l = 0; l < MATRIX_DIM; ++l) {
Expand Down
19 changes: 10 additions & 9 deletions c_source/Python_extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ void debug_alignment(char* name, void* profile, const char* s2, int ls2,
Options* options) {
char* dns2 = denormalize(s2, ls2);
printf(
"Aligning %s: profile = %lu, s2 = %s, len(s2) = %d, gapOpen = %f, gapExt = %f, threshold = %f\n",
name, (unsigned long) profile, dns2, ls2, options->gapOpen,
"Aligning %s: profile = %p, s2 = %s, len(s2) = %d, gapOpen = %f, gapExt = %f, threshold = %f\n",
name, profile, dns2, ls2, options->gapOpen,
options->gapExt, options->threshold);
free(dns2);
}
Expand All @@ -46,8 +46,8 @@ void debug_profile(char* name, void* pb, const char* q, int queryLen,
int i, j;

printf(
"Successfully created %s profile at %lu from query = %s, len(query) = %d\n",
name, (unsigned long) pb, query, queryLen);
"Successfully created %s profile at %p from query = %s, len(query) = %d\n",
name, pb, query, queryLen);
printf("The matrix used for the %s profile: \n", name);

for (i = 0; i < 26; ++i) {
Expand Down Expand Up @@ -114,13 +114,13 @@ ProfileShort* c_create_profile_short_sse_local(const char* query, int queryLen,

void c_free_profile_byte_sse_local(ProfileByte* profile) {
#ifdef PY_DEBUG
printf("Freeing BYTE profile at %lu\n", (unsigned long)profile);
printf("Freeing BYTE profile at %p\n", (void*) profile);
#endif
swps3_freeProfileByteSSE(profile);
}
void c_free_profile_short_sse_local(ProfileShort* profile) {
#ifdef PY_DEBUG
printf("Freeing SHORT profile at %lu\n", (unsigned long)profile);
printf("Freeing SHORT profile at %p\n", (void*) profile);
#endif
swps3_freeProfileShortSSE(profile);
}
Expand Down Expand Up @@ -189,7 +189,8 @@ int c_align_strings(double* matrix, char *s1, int len1, char *s2, int len2,

/* equal length, try to see if there is a match without deletions */
if (len1 == len2 && s1 != s2) {
for (i = tot = 0; i < len1; i++)
tot = 0;
for (i = 0; i < len1; i++)
tot += matrix[s1[i] * MATRIX_DIM + s2[i]];
if (tot >= escore) {
for (i = 0; i < len1; i++) {
Expand All @@ -216,14 +217,14 @@ int c_align_strings(double* matrix, char *s1, int len1, char *s2, int len2,

/* reverse strings */
if (s1 - s2 >= 0 && s2 + len2 - s1 > 0) {
j = MMAX(s1 - s2 + len1, len2);
j = (int) MMAX(s1 - s2 + len1, len2);
tprs1 = (char*) malloc(j * sizeof(char));
for (i = 0; i < j; i++)
tprs1[j - 1 - i] = s2[i];
prs1 = tprs1 + (j + s2 - s1 - len1);
prs2 = tprs1 + (j - len2);
} else if (s2 - s1 >= 0 && s1 + len1 - s2 > 0) {
j = MMAX(s2 - s1 + len2, len1);
j = (int) MMAX(s2 - s1 + len2, len1);
tprs1 = (char*) malloc(j * sizeof(char));
for (i = 0; i < j; i++)
tprs1[j - 1 - i] = s1[i];
Expand Down
30 changes: 15 additions & 15 deletions pyopa/backend/pyopa.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -313,8 +313,8 @@ cpdef align_strings(s1, s2, env, is_global=False, provided_alignment=None):

cdef np.ndarray[np.double_t,ndim=2] matrix = env.float64_matrix

max_len = cpyopa.c_align_strings(<double*> matrix.data, s1.encode('utf-8'), len(s1),
s2.encode('utf-8'), len(s2), provided_alignment[0], o1, o2, 0.5e-4, env.gap_open, env.gap_ext)
max_len = cpyopa.c_align_strings(<double*> matrix.data, s1.encode('utf-8'), <int> len(s1),
s2.encode('utf-8'), <int> len(s2), provided_alignment[0], o1, o2, 0.5e-4, env.gap_open, env.gap_ext)

o1_p = [0] * max_len
o2_p = [0] * max_len
Expand Down Expand Up @@ -343,7 +343,7 @@ def align_double(s1, s2, env, stop_at_threshold=False, is_global=False, calculat
profile1.create_profile_double(s1, env.float64_matrix)

if is_global:
res = c_align_double_normalized_global(env.float64_matrix, s1.s_norm.encode('utf-8'), len(s1), s2.s_norm.encode('utf-8'), len(s2),
res = c_align_double_normalized_global(env.float64_matrix, s1.s_norm.encode('utf-8'), <int> len(s1), s2.s_norm.encode('utf-8'), <int> len(s2),
env.gap_open, env.gap_ext)
if calculate_ranges :
res.extend([0, 0])
Expand Down Expand Up @@ -380,7 +380,7 @@ def align_scalar_reference_local(s1, s2, env):
:param env: the AlignmentEnvironment to be used
:return: the exact scalar score
"""
return c_align_scalar_normalized_reference_local(env.float64_matrix, s1.s_norm.encode('utf-8'), len(s1), s2.s_norm.encode('utf-8'), len(s2),
return c_align_scalar_normalized_reference_local(env.float64_matrix, s1.s_norm.encode('utf-8'), <int> len(s1), s2.s_norm.encode('utf-8'), <int> len(s2),
env.gap_open, env.gap_ext, env.threshold)

cpdef generate_env(log_pam1_env, new_pam, threshold=85.0):
Expand Down Expand Up @@ -534,7 +534,7 @@ cdef class AlignmentProfile:
if self._c_profileByte is not NULL:
cpyopa.c_free_profile_byte_sse_local(self._c_profileByte)

self._c_profileByte = cpyopa.c_create_profile_byte_sse_local(query.encode('utf-8'), len(query), <signed char*> matrix.data)
self._c_profileByte = cpyopa.c_create_profile_byte_sse_local(query.encode('utf-8'), <int> len(query), <signed char*> matrix.data)


cpdef create_profile_short(self, query, np.ndarray[np.int16_t,ndim=2] matrix):
Expand All @@ -554,7 +554,7 @@ cdef class AlignmentProfile:
if self._c_profileShort is not NULL:
cpyopa.c_free_profile_short_sse_local(self._c_profileShort)

self._c_profileShort = cpyopa.c_create_profile_short_sse_local(query.encode('utf-8'), len(query),
self._c_profileShort = cpyopa.c_create_profile_short_sse_local(query.encode('utf-8'), <int> len(query),
<signed short*> matrix.data)


Expand All @@ -574,7 +574,7 @@ cdef class AlignmentProfile:
if self._c_profileDouble is not NULL:
cpyopa.free_profile_double_sse(self._c_profileDouble)

self._c_profileDouble = cpyopa.createProfileDoubleSSE(query.encode('utf-8'), len(query), <double*> matrix.data)
self._c_profileDouble = cpyopa.createProfileDoubleSSE(query.encode('utf-8'), <int> len(query), <double*> matrix.data)


cpdef align_byte(self, s2, env):
Expand All @@ -589,7 +589,7 @@ cdef class AlignmentProfile:

s2 = s2.s_norm

ret = cpyopa.c_align_profile_byte_sse_local(self._c_profileByte, s2.encode('utf-8'), len(s2),
ret = cpyopa.c_align_profile_byte_sse_local(self._c_profileByte, s2.encode('utf-8'), <int> len(s2),
env.int8_gap_open, env.int8_gap_ext, env.threshold)
#do not scale back DBL_MAX
if ret >= sys.float_info.max:
Expand All @@ -610,7 +610,7 @@ cdef class AlignmentProfile:
s2 = s2.s_norm

ret = cpyopa.c_align_profile_short_sse_local(self._c_profileShort,
s2.encode('utf-8'), len(s2), env.int16_gap_open, env.int16_gap_ext, env.threshold)
s2.encode('utf-8'), <int> len(s2), env.int16_gap_open, env.int16_gap_ext, env.threshold)
#do not scale back DBL_MAX
if ret >= sys.float_info.max:
return ret
Expand Down Expand Up @@ -641,7 +641,7 @@ cdef class AlignmentProfile:

if threshold is None:
threshold = env.threshold if stop_at_threshold else sys.float_info.max
res = cpyopa.align_double_local(self._c_profileDouble, s2.encode('utf-8'), len(s2),
res = cpyopa.align_double_local(self._c_profileDouble, s2.encode('utf-8'), <int> len(s2),
env.gap_open, env.gap_ext, threshold, max1, max2)

ret.append(res)
Expand Down Expand Up @@ -778,7 +778,7 @@ cdef class MutipleAlEnv:
Q2 = Q @ Q
Q3 = Q @ Q2

self._pam_model.nlam = len(active)
self._pam_model.nlam = <int> len(active)
for i in range(26):
self._pam_model.lam[i] = lam[i] if i < len(active) else 0.0
for j in range(26):
Expand All @@ -799,7 +799,7 @@ cdef class MutipleAlEnv:
def create_day_matrices(self, envs):
self.free()

self.dms_len = len(envs)
self.dms_len = <int> len(envs)
cdef np.ndarray[np.double_t, ndim=1, mode="c"] gap_open = np.empty(shape=(self.dms_len + 1))
cdef np.ndarray[np.double_t, ndim=1, mode="c"] gap_ext = np.empty(shape=(self.dms_len + 1))
cdef np.ndarray[np.double_t, ndim=1, mode="c"] pam_dist = np.empty(shape=(self.dms_len + 1))
Expand Down Expand Up @@ -830,7 +830,7 @@ cdef class MutipleAlEnv:
raise RuntimeError('The day matrices have been freed')

cdef double result[3]
cpyopa.EstimatePamEigen(s1.s_norm.encode('utf-8'), s2.s_norm.encode('utf-8'), len(s1),
cpyopa.EstimatePamEigen(s1.s_norm.encode('utf-8'), s2.s_norm.encode('utf-8'), <int> len(s1),
self._c_dayMatrices, self.dms_len, &self._pam_model, result)
return [result[0], result[1], result[2]]

Expand All @@ -849,7 +849,7 @@ cdef class MutipleAlEnv:
cdef double result[3]
cdef np.ndarray[np.double_t, ndim=2, mode="c"] logpam = self.log_pam1.float64_matrix

cpyopa.EstimatePam(s1.s_norm.encode('utf-8'), s2.s_norm.encode('utf-8'), len(s1),
cpyopa.EstimatePam(s1.s_norm.encode('utf-8'), s2.s_norm.encode('utf-8'), <int> len(s1),
self._c_dayMatrices, self.dms_len, <double*> logpam.data, result)
return [result[0], result[1], result[2]]

Expand All @@ -871,7 +871,7 @@ cdef class MutipleAlEnv:
if n < 2:
raise ValueError('Must pass atleast 2 sequences!')

cdef int seq_len = len(seqs[0])
cdef int seq_len = <int> len(seqs[0])
for i in range(1, n):
if len(seqs[i]) != seq_len:
raise ValueError('All lengths must be equal!')
Expand Down
9 changes: 7 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ include = ["pyopa*"]
[tool.cibuildwheel]
build-frontend = "build[uv]"
build-verbosity = 1
# the Cython extension is not declared free-threading compatible yet
skip = ["cp3*t-*"]
# the Cython extension is not declared free-threading compatible yet;
# no 32-bit windows wheels; numpy has no windows arm64 wheels for Python 3.10
skip = ["cp3*t-*", "*-win32", "cp310-win_arm64"]
test-groups = ["test"]
test-command = "pytest -v {project}/test"
# fail instead of silently building wheels without parallel support
Expand All @@ -64,3 +65,7 @@ before-all = "bash tools/build_libomp_macos.sh /tmp/libomp"
# set explicitly: before-all otherwise gets MACOSX_DEPLOYMENT_TARGET=10.9 from
# cibuildwheel, but libomp needs >= 10.15 (and arm64 needs >= 11.0 anyway)
environment = { PYOPA_REQUIRE_OPENMP = "1", OPENMP_PREFIX = "/tmp/libomp", MACOSX_DEPLOYMENT_TARGET = "11.0" }

[tool.cibuildwheel.windows]
# bundle the MSVC OpenMP runtime (vcomp140.dll)
repair-wheel-command = "uvx delvewheel repair -w {dest_dir} {wheel}"
Loading