Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ polychrom/_polymer_math.cpp
*.dat
.idea
dist
polychrom/*.so
1 change: 0 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ def setup(app):
"simtk.unit",
"simtk.unit.nanometer",
"simtk.openmm",
"joblib",
"scipy.interpolate.fitpack2",
]
for mod_name in MOCK_MODULES:
Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Polychrom requires OpenMM, which can be installed through conda: ``conda install

CUDA is the fastest GPU-assisted backend to OpenMM. You would need to have the required version of CUDA, or install OpenMM compiled for your version of CUDA.

Other dependencies are simple, and are listed in requirements.txt. All but joblib are installable from either conda/pip, and joblib installs well with pip.
Other dependencies are simple, and are listed in requirements.txt. All are installable through pip or conda.

Installation errors and possible fixes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
18 changes: 18 additions & 0 deletions polychrom/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,19 @@
__version__ = "0.1.1"

# Check for OpenMM installation
try:
import openmm
except ImportError:
import warnings
warnings.warn(
"\n"
"OpenMM is not installed. Polychrom requires OpenMM for molecular dynamics simulations.\n"
"\n"
"Please install OpenMM with the appropriate backend for your system:\n"
" - For CUDA: pip install openmm[cuda13]\n"
" - For CPU: pip install openmm\n"
"\n"
"Visit https://openmm.org for more information.",
ImportWarning,
stacklevel=2
)
1 change: 0 additions & 1 deletion polychrom/__polymer_math.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
#include <cmath>
#include <vector>
#include <ctime>
#include <omp.h>

using namespace std;

Expand Down
3 changes: 1 addition & 2 deletions polychrom/cli/show
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import os
import sys
import tempfile

import joblib
import numpy as np

usage = """
Expand Down Expand Up @@ -43,7 +42,7 @@ start, end, step will basically select data[start:end:step]


if len(sys.argv) < 2:
print(useage)
print(usage)
exit()


Expand Down
1 change: 0 additions & 1 deletion polychrom/cli/xyz
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import sys
import tempfile
import textwrap

import joblib
import numpy as np

usage = """
Expand Down
1 change: 1 addition & 0 deletions polychrom/legacy/polymerutils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from polychrom.polymerutils import * # noqa: F403
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using wildcard imports (import *) can make it unclear what symbols are being imported and can cause namespace pollution. Consider explicitly importing the specific functions or modules needed, or at minimum document what this legacy module is expected to provide.

Suggested change
from polychrom.polymerutils import * # noqa: F403
# Legacy compatibility shim: re-exporting all public symbols from polychrom.polymerutils.
# If you know the specific symbols needed, replace the wildcard import below with explicit imports.
from polychrom.polymerutils import (
# List the specific functions, classes, or variables to import here, e.g.:
# function1,
# function2,
# Class1,
# ...
)

Copilot uses AI. Check for mistakes.
19 changes: 14 additions & 5 deletions polychrom/polymer_analyses.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
------------------------------

The main function calculating contacts is: :py:func:`polychrom.polymer_analyses.calculate_contacts`
Right now it is a simple wrapper around scipy.cKDTree.
Right now it is a simple wrapper around scipy.KDTree.

Another function :py:func:`polychrom.polymer_analyses.smart_contacts` was added recently to help build contact maps
with a large contact radius. It randomly sub-samples the monomers; by default selecting N/cutoff monomers. It then
Expand All @@ -37,7 +37,7 @@

import numpy as np
import pandas as pd
from scipy.spatial import cKDTree
from scipy.spatial import KDTree
from scipy.ndimage import gaussian_filter1d

try:
Expand Down Expand Up @@ -66,7 +66,7 @@ def calculate_contacts(data, cutoff=1.7):
if np.isnan(data).any():
raise RuntimeError("Data contains NANs")

tree = cKDTree(data)
tree = KDTree(data)
pairs = tree.query_pairs(cutoff, output_type="ndarray")
return pairs

Expand Down Expand Up @@ -573,7 +573,7 @@ def calculate_cistrans(data, chains, chain_id=0, cutoff=5, pbc_box=False, box_si
chain_end = chains[chain_id][1]

# all contact pairs available in the scaled data
tree = cKDTree(data_scaled, boxsize=box_size)
tree = KDTree(data_scaled, boxsize=box_size)
pairs = tree.query_pairs(cutoff, output_type="ndarray")

# total number of contacts of the marked chain:
Expand All @@ -582,7 +582,7 @@ def calculate_cistrans(data, chains, chain_id=0, cutoff=5, pbc_box=False, box_si
all_signal = len(pairs[pairs < chain_end]) - len(pairs[pairs < chain_start])

# contact pairs of the marked chain with itself
tree = cKDTree(data[chain_start:chain_end], boxsize=None)
tree = KDTree(data[chain_start:chain_end], boxsize=None)
pairs = tree.query_pairs(cutoff, output_type="ndarray")

# doubled number of contacts of the marked chain with itself (i.e. cis signal)
Expand All @@ -593,3 +593,12 @@ def calculate_cistrans(data, chains, chain_id=0, cutoff=5, pbc_box=False, box_si
trans_signal = all_signal - cis_signal

return cis_signal, trans_signal


def rotation_matrix(rotate):
"""Calculates rotation matrix based on three rotation angles"""
tx, ty, tz = rotate
Rx = np.array([[1, 0, 0], [0, np.cos(tx), -np.sin(tx)], [0, np.sin(tx), np.cos(tx)]])
Ry = np.array([[np.cos(ty), 0, -np.sin(ty)], [0, 1, 0], [np.sin(ty), 0, np.cos(ty)]])
Rz = np.array([[np.cos(tz), -np.sin(tz), 0], [np.sin(tz), np.cos(tz), 0], [0, 0, 1]])
return np.dot(Rx, np.dot(Ry, Rz))
106 changes: 26 additions & 80 deletions polychrom/polymerutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,33 +24,23 @@
xyz = data["pos"]
"""


from __future__ import absolute_import, division, print_function, unicode_literals

import glob
import io
import os
import warnings

import joblib
import numpy as np
import six

from polychrom.hdf5_format import load_URI

from . import hdf5_format


def load(filename):
"""Universal load function for any type of data file It always returns just XYZ
positions - use fetch_block or hdf5_format.load_URI for loading the whole metadata

Accepted file types
-------------------

New-style URIs (HDF5 based storage)

Text files in openmm-polymer format
joblib files in openmm-polymer format
"""
A function to load a single conformation from a URI. Deprecated.
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring should be formatted properly. It should start with a brief one-line summary followed by a blank line, then additional details.

Suggested change
A function to load a single conformation from a URI. Deprecated.
A function to load a single conformation from a URI. Deprecated.

Copilot uses AI. Check for mistakes.
Use load_URI from hdf5_format instead.

Parameters
----------
Expand All @@ -59,37 +49,18 @@ def load(filename):
filename to load or a URI

"""
warnings.warn("polymerutils.load is deprecated. Use hdf5_format.load_URI instead.", DeprecationWarning)
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deprecation warning should include a stacklevel parameter (typically stacklevel=2) to show the warning at the caller's location rather than within the deprecated function itself, making it more useful for users.

Suggested change
warnings.warn("polymerutils.load is deprecated. Use hdf5_format.load_URI instead.", DeprecationWarning)
warnings.warn("polymerutils.load is deprecated. Use hdf5_format.load_URI instead.", DeprecationWarning, stacklevel=2)

Copilot uses AI. Check for mistakes.

if "::" in filename:
return hdf5_format.load_URI(filename)["pos"]

if not os.path.exists(filename):
raise IOError("File not found :( \n %s" % filename)

try: # loading from a joblib file here
return dict(joblib.load(filename)).pop("data")
except Exception: # checking for a text file
data_file = open(filename)
line0 = data_file.readline()
try:
N = int(line0)
except (ValueError, UnicodeDecodeError):
raise TypeError("Could not read the file. Not text or joblib.")
data = [list(map(float, i.split())) for i in data_file.readlines()]

if len(data) != N:
raise ValueError("N does not correspond to the number of lines!")
return np.array(data)
raise ValueError("Only URIs are supported in this version of polychrom")


def fetch_block(folder, ind, full_output=False):
"""
A more generic function to fetch block number "ind" from a trajectory in a folder


This function is useful both if you want to load both "old style" trajectories (block1.dat),
and "new style" trajectories ("blocks_1-50.h5")

It will be used in files "show"
A function to fetch a single block from a folder with a new-style trajectory.
Old-style trajectores are deprecated.

Parameters
----------
Expand All @@ -109,12 +80,14 @@ def fetch_block(folder, ind, full_output=False):

if full_output==True, then dict with data and metadata; XYZ is under key "pos"
"""
warnings.warn(
"fetch_block is deprecated. Use hdf5_format.list_uris followed by hdf5_format.load_URI instead.",
DeprecationWarning,
)
Comment on lines +83 to +86
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deprecation warning should include a stacklevel parameter (typically stacklevel=2) to show the warning at the caller's location rather than within the deprecated function itself, making it more useful for users.

Copilot uses AI. Check for mistakes.

blocksh5 = glob.glob(os.path.join(folder, "blocks*.h5"))
blocksdat = glob.glob(os.path.join(folder, "block*.dat"))
ind = int(ind)
if (len(blocksh5) > 0) and (len(blocksdat) > 0):
raise ValueError("both .h5 and .dat files found in folder - exiting")
if (len(blocksh5) == 0) and (len(blocksdat) == 0):
if len(blocksh5) == 0:
raise ValueError("no blocks found")

if len(blocksh5) > 0:
Expand All @@ -129,45 +102,21 @@ def fetch_block(folder, ind, full_output=False):
pos = exists.index(True)
block = load_URI(blocksh5[pos] + f"::{ind}")
if not full_output:
block = block["pos"]
return block["pos"]
return block

Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing return statement when full_output=True for HDF5 files. When full_output=True, the function should return the full block dictionary but currently falls through without returning it. Add return block after line 115 or change line 115 to return block['pos'] and add an else: return block clause.

Suggested change
return block

Copilot uses AI. Check for mistakes.
if len(blocksdat) > 0:
block = load(os.path.join(folder, f"block{ind}.dat"))
return block
raise ValueError(f"Cannot find the block {ind} in the folder {folder}")


def save(data, filename, mode="txt", pdbGroups=None):
"""
Basically unchanged polymerutils.save function from openmm-polymer

It can save into txt or joblib formats used by old openmm-polymer

It is also very useful for saving files to PDB format to make them compatible
with nglview, pymol_show and others
A legacy function, currently only kept for compatibility with PDB saving that is rarely used.
"""
data = np.asarray(data, dtype=np.float32)
warnings.warn("polymerutils.save is deprecated. Will be moved to legacy", DeprecationWarning)

if mode.lower() == "joblib":
joblib.dump({"data": data}, filename=filename, compress=9)
return

if mode.lower() == "txt":
lines = [str(len(data)) + "\n"]

for particle in data:
lines.append("{0:.3f} {1:.3f} {2:.3f}\n".format(*particle))
if filename is None:
return lines

elif isinstance(filename, six.string_types):
with open(filename, "w") as myfile:
myfile.writelines(lines)
elif hasattr(filename, "writelines"):
filename.writelines(lines)
else:
raise ValueError("Not sure what to do with filename {0}".format(filename))
data = np.asarray(data, dtype=np.float32)

elif mode == "pdb":
if mode == "pdb":
data = data - np.minimum(np.min(data, axis=0), np.zeros(3, float) - 100)[None, :]
retret = ""

Expand Down Expand Up @@ -212,13 +161,10 @@ def add(st, n):
filename.write("C {0} {1} {2}".format(*i))

else:
raise ValueError("Unknown mode : %s, use h5dict, joblib, txt or pdb" % mode)
raise ValueError(f"Unknown mode {mode}. Only 'pdb' and 'pyxyz' are supported.")


def rotation_matrix(rotate):
"""Calculates rotation matrix based on three rotation angles"""
tx, ty, tz = rotate
Rx = np.array([[1, 0, 0], [0, np.cos(tx), -np.sin(tx)], [0, np.sin(tx), np.cos(tx)]])
Ry = np.array([[np.cos(ty), 0, -np.sin(ty)], [0, 1, 0], [np.sin(ty), 0, np.cos(ty)]])
Rz = np.array([[np.cos(tz), -np.sin(tz), 0], [np.sin(tz), np.cos(tz), 0], [0, 0, 1]])
return np.dot(Rx, np.dot(Ry, Rz))
warnings.warn("rotation_matrix will be moved to polymer_analyses", DeprecationWarning)
from polychrom.polymer_analyses import rotation_matrix as rm
return rm(rotate)
58 changes: 54 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,59 @@
[build-system]
requires = ["setuptools>=45", "wheel", "cython>=0.29", "numpy>=1.9"]
build-backend = "setuptools.build_meta"

[project]
name = "polychrom"
version = "0.1.1"
description = "A library for polymer simulations and their analyses."
readme = "README.md"
requires-python = ">=3.7"
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description claims 'Bumped python version to 3.13', but requires-python is set to '>=3.7'. This is inconsistent. If the intention is to require Python 3.13 as the minimum version, this should be requires-python = '>=3.13'. If Python 3.7+ is the correct minimum version, the PR description should be updated to reflect that Python 3.13 is added to the classifiers (supported versions) rather than being the minimum requirement.

Suggested change
requires-python = ">=3.7"
requires-python = ">=3.13"

Copilot uses AI. Check for mistakes.
license = "MIT"
authors = [
{name = "Mirny Lab", email = "espresso@mit.edu"}
]
keywords = ["genomics", "polymer", "Hi-C", "molecular dynamics", "chromosomes"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Science/Research",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]

dependencies = [
"cython>=0.29",
"numpy>=1.9",
"scipy>=0.16",
"h5py>=2.5",
"pandas>=0.19",
]

[project.optional-dependencies]
dev = [
"pytest",
"black",
"isort",
]

[project.urls]
Homepage = "https://github.com/open2c/polychrom"
Repository = "https://github.com/open2c/polychrom"

[project.scripts]
polychrom = "polychrom.cli:cli"

[tool.setuptools.packages.find]
include = ["polychrom*"]
exclude = ["utilities*", "build*", "docs*", "examples*"]

[tool.black]
line-length = 120

[tool.isort]
profile = "black"

[build-system]
requires = ["setuptools", "setuptools-scm", "cython"]
build-backend = "setuptools.build_meta"
3 changes: 0 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
six
cython
numpy>=1.9
scipy>=0.16
h5py>=2.5
pandas>=0.19
joblib
pyknotid
pytest
Loading