dense-arrays 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dense_arrays-0.2.0/CITATION.cff +12 -0
- dense_arrays-0.2.0/LICENSE +21 -0
- dense_arrays-0.2.0/MANIFEST.in +2 -0
- dense_arrays-0.2.0/PKG-INFO +99 -0
- dense_arrays-0.2.0/README.md +69 -0
- dense_arrays-0.2.0/docs/assets/dense-arrays-banner.png +0 -0
- dense_arrays-0.2.0/pyproject.toml +90 -0
- dense_arrays-0.2.0/setup.cfg +4 -0
- dense_arrays-0.2.0/src/dense_arrays/__init__.py +51 -0
- dense_arrays-0.2.0/src/dense_arrays/_record_validation.py +138 -0
- dense_arrays-0.2.0/src/dense_arrays/cli.py +202 -0
- dense_arrays-0.2.0/src/dense_arrays/constraints.py +159 -0
- dense_arrays-0.2.0/src/dense_arrays/errors.py +24 -0
- dense_arrays-0.2.0/src/dense_arrays/greedy.py +74 -0
- dense_arrays-0.2.0/src/dense_arrays/model.py +328 -0
- dense_arrays-0.2.0/src/dense_arrays/optimizer.py +696 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/__init__.py +47 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/__main__.py +5 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/cli.py +128 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/duplex_drawing.py +154 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/duplex_frames.py +181 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/duplex_geometry.py +111 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/export.py +200 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/frame_schedule.py +93 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/gif_writer.py +18 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/graph/__init__.py +80 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/graph/curves.py +124 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/graph/edge_routing.py +93 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/graph/geometry.py +102 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/graph/isotropic_layout.py +422 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/graph/labels.py +283 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/graph/layout.py +347 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/graph/layout_candidates.py +179 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/graph/model.py +163 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/graph/obstacles.py +180 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/graph/presentation.py +38 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/graph/projection.py +107 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/graph/routing.py +118 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/graph_drawing.py +341 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/graph_layout.py +80 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/matplotlib_renderer.py +110 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/models.py +269 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/output.py +89 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/positions.py +52 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/presentation.py +172 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/reconstruction.py +195 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/scene_drawing.py +443 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/serialization.py +394 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/theme.py +152 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/timeline.py +55 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/typography.py +47 -0
- dense_arrays-0.2.0/src/dense_arrays/playback/validation.py +155 -0
- dense_arrays-0.2.0/src/dense_arrays/problem.py +111 -0
- dense_arrays-0.2.0/src/dense_arrays/realized.py +221 -0
- dense_arrays-0.2.0/src/dense_arrays/sequence.py +149 -0
- dense_arrays-0.2.0/src/dense_arrays/solution.py +201 -0
- dense_arrays-0.2.0/src/dense_arrays.egg-info/PKG-INFO +99 -0
- dense_arrays-0.2.0/src/dense_arrays.egg-info/SOURCES.txt +78 -0
- dense_arrays-0.2.0/src/dense_arrays.egg-info/dependency_links.txt +1 -0
- dense_arrays-0.2.0/src/dense_arrays.egg-info/entry_points.txt +3 -0
- dense_arrays-0.2.0/src/dense_arrays.egg-info/requires.txt +19 -0
- dense_arrays-0.2.0/src/dense_arrays.egg-info/top_level.txt +1 -0
- dense_arrays-0.2.0/tests/test_cli.py +102 -0
- dense_arrays-0.2.0/tests/test_core_contracts.py +207 -0
- dense_arrays-0.2.0/tests/test_documentation.py +114 -0
- dense_arrays-0.2.0/tests/test_greedy.py +69 -0
- dense_arrays-0.2.0/tests/test_optimize.py +563 -0
- dense_arrays-0.2.0/tests/test_optional_playback_imports.py +39 -0
- dense_arrays-0.2.0/tests/test_playback.py +344 -0
- dense_arrays-0.2.0/tests/test_playback_cli.py +169 -0
- dense_arrays-0.2.0/tests/test_playback_contracts.py +365 -0
- dense_arrays-0.2.0/tests/test_playback_curves.py +27 -0
- dense_arrays-0.2.0/tests/test_playback_exports.py +364 -0
- dense_arrays-0.2.0/tests/test_playback_graph.py +76 -0
- dense_arrays-0.2.0/tests/test_playback_output.py +59 -0
- dense_arrays-0.2.0/tests/test_playback_presentation.py +225 -0
- dense_arrays-0.2.0/tests/test_playback_resting.py +381 -0
- dense_arrays-0.2.0/tests/test_playback_typography.py +329 -0
- dense_arrays-0.2.0/tests/test_release.py +79 -0
- dense_arrays-0.2.0/tests/test_solver_outcomes.py +178 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: Cite Dense Arrays and retain the exact release or source revision used.
|
|
3
|
+
type: software
|
|
4
|
+
title: Dense Arrays
|
|
5
|
+
authors:
|
|
6
|
+
- family-names: Andreani
|
|
7
|
+
given-names: Virgile
|
|
8
|
+
- family-names: South
|
|
9
|
+
given-names: Eric J.
|
|
10
|
+
repository-code: https://github.com/e-south/dense-arrays
|
|
11
|
+
url: https://dunloplab.gitlab.io/dense-arrays
|
|
12
|
+
license: MIT
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Dunlop lab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dense-arrays
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A library to create densely packed DNA arrays from motifs
|
|
5
|
+
Author-email: Virgile Andreani <andreani@bu.edu>, Eric J South <ericjohnsouth@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/e-south/dense-arrays
|
|
8
|
+
Project-URL: Documentation, https://dunloplab.gitlab.io/dense-arrays
|
|
9
|
+
Project-URL: Issues, https://github.com/e-south/dense-arrays/issues
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: numpy>=1.25.2
|
|
14
|
+
Requires-Dist: ortools>=9.7.2996
|
|
15
|
+
Requires-Dist: rich>=13.7.0
|
|
16
|
+
Requires-Dist: typer>=0.12.0
|
|
17
|
+
Provides-Extra: playback
|
|
18
|
+
Requires-Dist: matplotlib>=3.8.0; extra == "playback"
|
|
19
|
+
Requires-Dist: networkx>=3.6.1; extra == "playback"
|
|
20
|
+
Provides-Extra: docs
|
|
21
|
+
Requires-Dist: mkdocs>=1.6.1; extra == "docs"
|
|
22
|
+
Requires-Dist: mkdocs-material>=9.7.3; extra == "docs"
|
|
23
|
+
Requires-Dist: mkdocstrings[python]>=1.0.3; extra == "docs"
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pre-commit>=4.5.1; extra == "dev"
|
|
26
|
+
Requires-Dist: pip-audit>=2.10.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest>=9.0.3; extra == "dev"
|
|
28
|
+
Requires-Dist: ruff>=0.14.14; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# 
|
|
32
|
+
|
|
33
|
+
[](https://github.com/e-south/dense-arrays/actions/workflows/ci.yml)
|
|
34
|
+
[](https://dunloplab.gitlab.io/dense-arrays)
|
|
35
|
+
|
|
36
|
+
Pack overlapping DNA motifs into a sequence-length limit. Dense Arrays selects
|
|
37
|
+
an arrangement and returns the sequence and each motif's position, so you can
|
|
38
|
+
inspect the overlaps or generate further solutions. Choose single- or
|
|
39
|
+
double-strand placement, require motif groups, or constrain positions.
|
|
40
|
+
|
|
41
|
+
The package also renders saved placements as images and video. [Watch the
|
|
42
|
+
worked playback](https://dunloplab.gitlab.io/dense-arrays/playback/#watch-four-overlapping-motifs)
|
|
43
|
+
to see how four motifs share sequence space.
|
|
44
|
+
|
|
45
|
+
## First array
|
|
46
|
+
|
|
47
|
+
With Python 3.12 or later and [uv](https://docs.astral.sh/uv/), run from a
|
|
48
|
+
[source checkout](https://github.com/e-south/dense-arrays/blob/main/docs/quickstart.md#install-from-source):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
uv sync --frozen
|
|
52
|
+
uv run dense-arrays optimize \
|
|
53
|
+
--motif ACGTTGCAAGTCCTGA \
|
|
54
|
+
--motif AGTCCTGATCGTACCG \
|
|
55
|
+
--motif TCGTACCGATGCTTAG \
|
|
56
|
+
--motif ATGCTTAGGACGTTCA \
|
|
57
|
+
--length 40 --strands double
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The search considers both strands. One optimal arrangement packs all four
|
|
61
|
+
16-base motifs into 40 bases:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
ACGTTGCAAGTCCTGATCGTACCGATGCTTAGGACGTTCA
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
In this orientation, the motifs start at 0, 8, 16, and 24. The solver may return
|
|
68
|
+
the reverse-complement arrangement. The
|
|
69
|
+
[quickstart](https://github.com/e-south/dense-arrays/blob/main/docs/quickstart.md) explains these offsets and shows Python use
|
|
70
|
+
and bounded enumeration of further solutions.
|
|
71
|
+
|
|
72
|
+
## Documentation
|
|
73
|
+
|
|
74
|
+
- [Create and read your first array](https://github.com/e-south/dense-arrays/blob/main/docs/quickstart.md).
|
|
75
|
+
- [Set positional and regulator constraints](https://github.com/e-south/dense-arrays/blob/main/docs/constraints.md).
|
|
76
|
+
- [Render saved placements as images or video](https://github.com/e-south/dense-arrays/blob/main/docs/playback.md).
|
|
77
|
+
- [Understand the packing method](https://github.com/e-south/dense-arrays/blob/main/docs/method.md).
|
|
78
|
+
- [Look up Python interfaces](https://github.com/e-south/dense-arrays/blob/main/docs/api.md).
|
|
79
|
+
- [Update an existing caller](https://github.com/e-south/dense-arrays/blob/main/docs/migration.md).
|
|
80
|
+
|
|
81
|
+
The [documentation index](https://github.com/e-south/dense-arrays/blob/main/docs/index.md) also routes integrators and
|
|
82
|
+
contributors to the relevant interfaces and checks.
|
|
83
|
+
|
|
84
|
+
## Citation
|
|
85
|
+
|
|
86
|
+
If you use Dense Arrays in your research, please cite:
|
|
87
|
+
|
|
88
|
+
Andreani V, South EJ, Dunlop MJ (2024). Generating information-dense promoter
|
|
89
|
+
sequences with optimal string packing. *PLOS Computational Biology* 20(7):
|
|
90
|
+
e1012276. [doi:10.1371/journal.pcbi.1012276](https://doi.org/10.1371/journal.pcbi.1012276).
|
|
91
|
+
|
|
92
|
+
Record the package version or commit alongside your results.
|
|
93
|
+
|
|
94
|
+
## Contribute
|
|
95
|
+
|
|
96
|
+
[Development](https://github.com/e-south/dense-arrays/blob/main/docs/development.md) covers local checks and documentation builds.
|
|
97
|
+
[Report bugs](https://github.com/e-south/dense-arrays/issues) and follow the
|
|
98
|
+
[security policy](https://github.com/e-south/dense-arrays/blob/main/SECURITY.md) for vulnerabilities.
|
|
99
|
+
Dense Arrays is available under the [MIT license](https://github.com/e-south/dense-arrays/blob/main/LICENSE).
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# 
|
|
2
|
+
|
|
3
|
+
[](https://github.com/e-south/dense-arrays/actions/workflows/ci.yml)
|
|
4
|
+
[](https://dunloplab.gitlab.io/dense-arrays)
|
|
5
|
+
|
|
6
|
+
Pack overlapping DNA motifs into a sequence-length limit. Dense Arrays selects
|
|
7
|
+
an arrangement and returns the sequence and each motif's position, so you can
|
|
8
|
+
inspect the overlaps or generate further solutions. Choose single- or
|
|
9
|
+
double-strand placement, require motif groups, or constrain positions.
|
|
10
|
+
|
|
11
|
+
The package also renders saved placements as images and video. [Watch the
|
|
12
|
+
worked playback](https://dunloplab.gitlab.io/dense-arrays/playback/#watch-four-overlapping-motifs)
|
|
13
|
+
to see how four motifs share sequence space.
|
|
14
|
+
|
|
15
|
+
## First array
|
|
16
|
+
|
|
17
|
+
With Python 3.12 or later and [uv](https://docs.astral.sh/uv/), run from a
|
|
18
|
+
[source checkout](https://github.com/e-south/dense-arrays/blob/main/docs/quickstart.md#install-from-source):
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
uv sync --frozen
|
|
22
|
+
uv run dense-arrays optimize \
|
|
23
|
+
--motif ACGTTGCAAGTCCTGA \
|
|
24
|
+
--motif AGTCCTGATCGTACCG \
|
|
25
|
+
--motif TCGTACCGATGCTTAG \
|
|
26
|
+
--motif ATGCTTAGGACGTTCA \
|
|
27
|
+
--length 40 --strands double
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The search considers both strands. One optimal arrangement packs all four
|
|
31
|
+
16-base motifs into 40 bases:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
ACGTTGCAAGTCCTGATCGTACCGATGCTTAGGACGTTCA
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
In this orientation, the motifs start at 0, 8, 16, and 24. The solver may return
|
|
38
|
+
the reverse-complement arrangement. The
|
|
39
|
+
[quickstart](https://github.com/e-south/dense-arrays/blob/main/docs/quickstart.md) explains these offsets and shows Python use
|
|
40
|
+
and bounded enumeration of further solutions.
|
|
41
|
+
|
|
42
|
+
## Documentation
|
|
43
|
+
|
|
44
|
+
- [Create and read your first array](https://github.com/e-south/dense-arrays/blob/main/docs/quickstart.md).
|
|
45
|
+
- [Set positional and regulator constraints](https://github.com/e-south/dense-arrays/blob/main/docs/constraints.md).
|
|
46
|
+
- [Render saved placements as images or video](https://github.com/e-south/dense-arrays/blob/main/docs/playback.md).
|
|
47
|
+
- [Understand the packing method](https://github.com/e-south/dense-arrays/blob/main/docs/method.md).
|
|
48
|
+
- [Look up Python interfaces](https://github.com/e-south/dense-arrays/blob/main/docs/api.md).
|
|
49
|
+
- [Update an existing caller](https://github.com/e-south/dense-arrays/blob/main/docs/migration.md).
|
|
50
|
+
|
|
51
|
+
The [documentation index](https://github.com/e-south/dense-arrays/blob/main/docs/index.md) also routes integrators and
|
|
52
|
+
contributors to the relevant interfaces and checks.
|
|
53
|
+
|
|
54
|
+
## Citation
|
|
55
|
+
|
|
56
|
+
If you use Dense Arrays in your research, please cite:
|
|
57
|
+
|
|
58
|
+
Andreani V, South EJ, Dunlop MJ (2024). Generating information-dense promoter
|
|
59
|
+
sequences with optimal string packing. *PLOS Computational Biology* 20(7):
|
|
60
|
+
e1012276. [doi:10.1371/journal.pcbi.1012276](https://doi.org/10.1371/journal.pcbi.1012276).
|
|
61
|
+
|
|
62
|
+
Record the package version or commit alongside your results.
|
|
63
|
+
|
|
64
|
+
## Contribute
|
|
65
|
+
|
|
66
|
+
[Development](https://github.com/e-south/dense-arrays/blob/main/docs/development.md) covers local checks and documentation builds.
|
|
67
|
+
[Report bugs](https://github.com/e-south/dense-arrays/issues) and follow the
|
|
68
|
+
[security policy](https://github.com/e-south/dense-arrays/blob/main/SECURITY.md) for vulnerabilities.
|
|
69
|
+
Dense Arrays is available under the [MIT license](https://github.com/e-south/dense-arrays/blob/main/LICENSE).
|
|
Binary file
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "dense-arrays"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "A library to create densely packed DNA arrays from motifs"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Virgile Andreani", email = "andreani@bu.edu"},
|
|
7
|
+
{name = "Eric J South", email = "ericjohnsouth@gmail.com"},
|
|
8
|
+
]
|
|
9
|
+
dependencies = [
|
|
10
|
+
"numpy>=1.25.2",
|
|
11
|
+
"ortools>=9.7.2996",
|
|
12
|
+
"rich>=13.7.0",
|
|
13
|
+
"typer>=0.12.0",
|
|
14
|
+
]
|
|
15
|
+
requires-python = ">=3.12"
|
|
16
|
+
readme = "README.md"
|
|
17
|
+
license = "MIT"
|
|
18
|
+
license-files = ["LICENSE"]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Repository = "https://github.com/e-south/dense-arrays"
|
|
22
|
+
Documentation = "https://dunloplab.gitlab.io/dense-arrays"
|
|
23
|
+
Issues = "https://github.com/e-south/dense-arrays/issues"
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
playback = [
|
|
27
|
+
"matplotlib>=3.8.0",
|
|
28
|
+
"networkx>=3.6.1",
|
|
29
|
+
]
|
|
30
|
+
docs = [
|
|
31
|
+
"mkdocs>=1.6.1",
|
|
32
|
+
"mkdocs-material>=9.7.3",
|
|
33
|
+
"mkdocstrings[python]>=1.0.3",
|
|
34
|
+
]
|
|
35
|
+
dev = [
|
|
36
|
+
"pre-commit>=4.5.1",
|
|
37
|
+
"pip-audit>=2.10.0",
|
|
38
|
+
"pytest>=9.0.3",
|
|
39
|
+
"ruff>=0.14.14",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[project.scripts]
|
|
43
|
+
dense-arrays = "dense_arrays.cli:app"
|
|
44
|
+
dense-arrays-playback = "dense_arrays.playback.cli:app"
|
|
45
|
+
|
|
46
|
+
[build-system]
|
|
47
|
+
requires = ["setuptools>=77.0.3", "wheel"]
|
|
48
|
+
build-backend = "setuptools.build_meta"
|
|
49
|
+
|
|
50
|
+
[tool.ruff.lint]
|
|
51
|
+
preview = true
|
|
52
|
+
explicit-preview-rules = true
|
|
53
|
+
select = ["ALL"]
|
|
54
|
+
ignore = [
|
|
55
|
+
# The formatter owns trailing commas.
|
|
56
|
+
"COM812",
|
|
57
|
+
# Implicitly concatenated string literals on one line
|
|
58
|
+
# (does not work well with ruff format)
|
|
59
|
+
"ISC001",
|
|
60
|
+
# Unnecessary assignment to return
|
|
61
|
+
"RET504",
|
|
62
|
+
# Commented code
|
|
63
|
+
"ERA001",
|
|
64
|
+
# Copyright stuff
|
|
65
|
+
"CPY001",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
[tool.ruff.lint.per-file-ignores]
|
|
69
|
+
"maintenance/release.py" = ["T201"]
|
|
70
|
+
# Optional visualization imports stay inside the functions that use them.
|
|
71
|
+
# Relative imports follow the playback package boundary.
|
|
72
|
+
"src/dense_arrays/playback/**/*.py" = [
|
|
73
|
+
"PLC0415",
|
|
74
|
+
"TID252",
|
|
75
|
+
]
|
|
76
|
+
"tests/test_optional_playback_imports.py" = ["S101", "S404", "S603"]
|
|
77
|
+
"src/dense_arrays/main.py" = ["T201"]
|
|
78
|
+
# Typer evaluates Path annotations and supplies option defaults at runtime.
|
|
79
|
+
"src/dense_arrays/cli.py" = ["FBT002"]
|
|
80
|
+
"src/dense_arrays/playback/cli.py" = ["TC003", "PLR0913", "PLR0917"]
|
|
81
|
+
# Keep the existing explicit media option signatures.
|
|
82
|
+
"src/dense_arrays/playback/matplotlib_renderer.py" = ["PLR0913"]
|
|
83
|
+
"src/dense_arrays/playback/export.py" = ["PLR0913"]
|
|
84
|
+
"tests/test_*.py" = ["ANN201", "D103", "S101", "PLR2004", "FBT001"]
|
|
85
|
+
|
|
86
|
+
[tool.ruff.lint.pydocstyle]
|
|
87
|
+
convention = "numpy"
|
|
88
|
+
|
|
89
|
+
[tool.ruff.lint.flake8-pytest-style]
|
|
90
|
+
parametrize-names-type = "csv"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Pack DNA motif libraries and describe their realized placements.
|
|
2
|
+
|
|
3
|
+
Module Author(s): Virgile Andreani, Eric J. South
|
|
4
|
+
Dunlop Lab
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from importlib import import_module
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from .errors import (
|
|
14
|
+
InfeasibleError,
|
|
15
|
+
InvalidSolverResultError,
|
|
16
|
+
OptimizationError,
|
|
17
|
+
SolverBackendError,
|
|
18
|
+
UnprovenSolutionError,
|
|
19
|
+
)
|
|
20
|
+
from .optimizer import Optimizer
|
|
21
|
+
from .solution import DenseArray
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"DenseArray",
|
|
25
|
+
"InfeasibleError",
|
|
26
|
+
"InvalidSolverResultError",
|
|
27
|
+
"OptimizationError",
|
|
28
|
+
"Optimizer",
|
|
29
|
+
"SolverBackendError",
|
|
30
|
+
"UnprovenSolutionError",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def __getattr__(name: str) -> object:
|
|
35
|
+
"""Load public classes on demand so playback does not import a solver.
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
object
|
|
40
|
+
The requested public class.
|
|
41
|
+
|
|
42
|
+
Raises
|
|
43
|
+
------
|
|
44
|
+
AttributeError
|
|
45
|
+
If the name is not part of the public package interface.
|
|
46
|
+
"""
|
|
47
|
+
if name not in __all__:
|
|
48
|
+
msg = f"module {__name__!r} has no attribute {name!r}"
|
|
49
|
+
raise AttributeError(msg)
|
|
50
|
+
module = {"Optimizer": "optimizer", "DenseArray": "solution"}.get(name, "errors")
|
|
51
|
+
return getattr(import_module(f"{__name__}.{module}"), name)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Validate scalar fields and immutable JSON snapshots for persisted records.
|
|
2
|
+
|
|
3
|
+
Module Author(s): Eric J. South
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import math
|
|
9
|
+
from collections.abc import Mapping, Sequence
|
|
10
|
+
from enum import StrEnum
|
|
11
|
+
from types import MappingProxyType
|
|
12
|
+
|
|
13
|
+
_IUPAC_DNA = frozenset("ACGTRYSWKMBDHVN")
|
|
14
|
+
_SHA256_LENGTH = 64
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def required_text(value: object, *, field_name: str) -> str:
|
|
18
|
+
"""Return nonblank text without coercing identities."""
|
|
19
|
+
if not isinstance(value, str):
|
|
20
|
+
msg = f"{field_name} must be a non-empty string"
|
|
21
|
+
raise TypeError(msg)
|
|
22
|
+
if not value.strip():
|
|
23
|
+
msg = f"{field_name} must be a non-empty string"
|
|
24
|
+
raise ValueError(msg)
|
|
25
|
+
return value
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def normalized_dna(value: object, *, field_name: str) -> str:
|
|
29
|
+
"""Return uppercase DNA after checking the IUPAC alphabet."""
|
|
30
|
+
sequence = required_text(value, field_name=field_name).strip().upper()
|
|
31
|
+
invalid = sorted(set(sequence) - _IUPAC_DNA)
|
|
32
|
+
if invalid:
|
|
33
|
+
msg = f"{field_name} contains non-IUPAC DNA symbols: {invalid}"
|
|
34
|
+
raise ValueError(msg)
|
|
35
|
+
return sequence
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def integer(value: object, *, field_name: str, minimum: int | None = None) -> int:
|
|
39
|
+
"""Return an integer within its declared domain, excluding booleans."""
|
|
40
|
+
if isinstance(value, bool) or not isinstance(value, int):
|
|
41
|
+
msg = f"{field_name} must be an integer"
|
|
42
|
+
raise TypeError(msg)
|
|
43
|
+
if minimum is not None and value < minimum:
|
|
44
|
+
msg = f"{field_name} must be an integer >= {minimum}"
|
|
45
|
+
raise ValueError(msg)
|
|
46
|
+
return value
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def enum_value[T: StrEnum](value: object, enum: type[T], *, field_name: str) -> T:
|
|
50
|
+
"""Return a supported enum value from an enum or exact string."""
|
|
51
|
+
required_text(value, field_name=field_name)
|
|
52
|
+
return enum(value)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def digest(value: object, *, field_name: str) -> str:
|
|
56
|
+
"""Return a canonical SHA-256 hexadecimal digest."""
|
|
57
|
+
text = required_text(value, field_name=field_name).strip().lower()
|
|
58
|
+
if len(text) != _SHA256_LENGTH or any(
|
|
59
|
+
char not in "0123456789abcdef" for char in text
|
|
60
|
+
):
|
|
61
|
+
msg = f"{field_name} must be a SHA-256 hex digest"
|
|
62
|
+
raise ValueError(msg)
|
|
63
|
+
return text
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def records[T](
|
|
67
|
+
value: object, record_type: type[T], *, field_name: str
|
|
68
|
+
) -> tuple[T, ...]:
|
|
69
|
+
"""Freeze a sequence of records after checking every member's type."""
|
|
70
|
+
if isinstance(value, (str, bytes)) or not isinstance(value, Sequence):
|
|
71
|
+
msg = f"{field_name} must be a sequence of {record_type.__name__} records"
|
|
72
|
+
raise TypeError(msg)
|
|
73
|
+
result = tuple(value)
|
|
74
|
+
if any(not isinstance(item, record_type) for item in result):
|
|
75
|
+
msg = f"{field_name} must contain only {record_type.__name__} records"
|
|
76
|
+
raise TypeError(msg)
|
|
77
|
+
return result
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _freeze_json(value: object, ancestors: frozenset[int]) -> object:
|
|
81
|
+
if value is None or isinstance(value, (str, bool, int)):
|
|
82
|
+
return value
|
|
83
|
+
if isinstance(value, float):
|
|
84
|
+
if math.isfinite(value):
|
|
85
|
+
return value
|
|
86
|
+
msg = "JSON provenance numbers must be finite"
|
|
87
|
+
raise ValueError(msg)
|
|
88
|
+
if id(value) in ancestors:
|
|
89
|
+
msg = "JSON provenance must not contain cyclic containers"
|
|
90
|
+
raise ValueError(msg)
|
|
91
|
+
parents = ancestors | {id(value)}
|
|
92
|
+
if isinstance(value, Mapping):
|
|
93
|
+
if any(not isinstance(key, str) for key in value):
|
|
94
|
+
msg = "JSON provenance object keys must be strings"
|
|
95
|
+
raise TypeError(msg)
|
|
96
|
+
return MappingProxyType(
|
|
97
|
+
{key: _freeze_json(item, parents) for key, item in value.items()}
|
|
98
|
+
)
|
|
99
|
+
if isinstance(value, (list, tuple)):
|
|
100
|
+
return tuple(_freeze_json(item, parents) for item in value)
|
|
101
|
+
msg = "provenance and metadata must contain only JSON values"
|
|
102
|
+
raise TypeError(msg)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def immutable_json_mapping(value: object) -> Mapping[str, object]:
|
|
106
|
+
"""Return a recursively immutable, detached JSON object snapshot."""
|
|
107
|
+
if not isinstance(value, Mapping):
|
|
108
|
+
msg = "provenance and metadata must be JSON objects"
|
|
109
|
+
raise TypeError(msg)
|
|
110
|
+
return _freeze_json(value, frozenset())
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def mutable_json(value: object) -> object:
|
|
114
|
+
"""Return independent JSON dictionaries and arrays from a frozen snapshot."""
|
|
115
|
+
if isinstance(value, Mapping):
|
|
116
|
+
return {key: mutable_json(item) for key, item in value.items()}
|
|
117
|
+
if isinstance(value, tuple):
|
|
118
|
+
return [mutable_json(item) for item in value]
|
|
119
|
+
return value
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def validate_placement_sequence(
|
|
123
|
+
*, placement_id: str, start: int, end: int, sequence: str, realized_sequence: str
|
|
124
|
+
) -> None:
|
|
125
|
+
"""Check placement bounds and exact alignment with the realized sequence."""
|
|
126
|
+
if end > len(realized_sequence):
|
|
127
|
+
msg = (
|
|
128
|
+
f"placement {placement_id!r} ends at {end}, "
|
|
129
|
+
f"beyond sequence length {len(realized_sequence)}"
|
|
130
|
+
)
|
|
131
|
+
raise ValueError(msg)
|
|
132
|
+
observed = realized_sequence[start:end]
|
|
133
|
+
if observed != sequence:
|
|
134
|
+
msg = (
|
|
135
|
+
f"placement {placement_id!r} is sequence-inconsistent: "
|
|
136
|
+
f"expected {sequence!r}, observed {observed!r}"
|
|
137
|
+
)
|
|
138
|
+
raise ValueError(msg)
|