ldetect-lite 0.1.0__py3-none-any.whl
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.
- ldetect_lite/__init__.py +8 -0
- ldetect_lite/_cli/__init__.py +0 -0
- ldetect_lite/_cli/cmd_covariance.py +75 -0
- ldetect_lite/_cli/cmd_covariance_summary.py +95 -0
- ldetect_lite/_cli/cmd_extract_bpoints.py +70 -0
- ldetect_lite/_cli/cmd_find_minima.py +150 -0
- ldetect_lite/_cli/cmd_interpolate_maps.py +61 -0
- ldetect_lite/_cli/cmd_matrix_to_vector.py +95 -0
- ldetect_lite/_cli/cmd_partition.py +70 -0
- ldetect_lite/_cli/cmd_run.py +415 -0
- ldetect_lite/_cli/main.py +64 -0
- ldetect_lite/_util/__init__.py +0 -0
- ldetect_lite/_util/binary_search.py +67 -0
- ldetect_lite/_util/covariance_array.py +1024 -0
- ldetect_lite/_util/covariance_summary.py +220 -0
- ldetect_lite/_util/intervals.py +125 -0
- ldetect_lite/_util/logging.py +26 -0
- ldetect_lite/_util/memory.py +60 -0
- ldetect_lite/_util/vector_array.py +908 -0
- ldetect_lite/filters.py +82 -0
- ldetect_lite/find_minima.py +159 -0
- ldetect_lite/interpolate_maps.py +165 -0
- ldetect_lite/io/__init__.py +0 -0
- ldetect_lite/io/bed.py +118 -0
- ldetect_lite/io/covariance.py +390 -0
- ldetect_lite/io/covariance_hdf5.py +714 -0
- ldetect_lite/io/partitions.py +110 -0
- ldetect_lite/io/vcf.py +17 -0
- ldetect_lite/local_search.py +1821 -0
- ldetect_lite/matrix_analysis.py +511 -0
- ldetect_lite/metric.py +265 -0
- ldetect_lite/pipeline.py +662 -0
- ldetect_lite/shrinkage.py +846 -0
- ldetect_lite-0.1.0.dist-info/METADATA +172 -0
- ldetect_lite-0.1.0.dist-info/RECORD +38 -0
- ldetect_lite-0.1.0.dist-info/WHEEL +4 -0
- ldetect_lite-0.1.0.dist-info/entry_points.txt +4 -0
- ldetect_lite-0.1.0.dist-info/licenses/LICENSE +21 -0
ldetect_lite/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""CLI: calc-covariance subcommand."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def register(subparsers: argparse._SubParsersAction) -> None: # type: ignore[type-arg]
|
|
11
|
+
p = subparsers.add_parser(
|
|
12
|
+
"calc-covariance",
|
|
13
|
+
help="Compute Wen/Stephens shrinkage LD from a VCF stream (reads stdin).",
|
|
14
|
+
)
|
|
15
|
+
p.add_argument(
|
|
16
|
+
"--genetic-map",
|
|
17
|
+
required=True,
|
|
18
|
+
type=Path,
|
|
19
|
+
metavar="PATH",
|
|
20
|
+
help="Gzipped genetic map (chr, position, cM).",
|
|
21
|
+
)
|
|
22
|
+
p.add_argument(
|
|
23
|
+
"--individuals",
|
|
24
|
+
required=True,
|
|
25
|
+
type=Path,
|
|
26
|
+
metavar="PATH",
|
|
27
|
+
help="Plain-text file; one individual ID per line.",
|
|
28
|
+
)
|
|
29
|
+
p.add_argument(
|
|
30
|
+
"--output",
|
|
31
|
+
required=True,
|
|
32
|
+
type=Path,
|
|
33
|
+
metavar="PATH",
|
|
34
|
+
help="Gzipped 8-column covariance output file.",
|
|
35
|
+
)
|
|
36
|
+
p.add_argument(
|
|
37
|
+
"--ne",
|
|
38
|
+
type=float,
|
|
39
|
+
default=11418.0,
|
|
40
|
+
metavar="FLOAT",
|
|
41
|
+
help="Effective population size (default: 11418.0).",
|
|
42
|
+
)
|
|
43
|
+
p.add_argument(
|
|
44
|
+
"--cutoff",
|
|
45
|
+
type=float,
|
|
46
|
+
default=1e-7,
|
|
47
|
+
metavar="FLOAT",
|
|
48
|
+
help="LD cutoff; pairs below this are not written (default: 1e-7).",
|
|
49
|
+
)
|
|
50
|
+
p.add_argument(
|
|
51
|
+
"--covariance-compression",
|
|
52
|
+
choices=("lzf", "zstd"),
|
|
53
|
+
default="zstd",
|
|
54
|
+
help=(
|
|
55
|
+
"HDF5 compression codec for the covariance partition. 'zstd' is "
|
|
56
|
+
"smaller and faster to read/write than 'lzf' at equal precision "
|
|
57
|
+
"(default: zstd)."
|
|
58
|
+
),
|
|
59
|
+
)
|
|
60
|
+
p.set_defaults(func=_run)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _run(args: argparse.Namespace) -> int:
|
|
64
|
+
from ldetect_lite.shrinkage import calc_covariance
|
|
65
|
+
|
|
66
|
+
calc_covariance(
|
|
67
|
+
vcf_stream=sys.stdin,
|
|
68
|
+
genetic_map_path=args.genetic_map,
|
|
69
|
+
individuals_path=args.individuals,
|
|
70
|
+
output_path=args.output,
|
|
71
|
+
ne=args.ne,
|
|
72
|
+
cutoff=args.cutoff,
|
|
73
|
+
compression=args.covariance_compression,
|
|
74
|
+
)
|
|
75
|
+
return 0
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""CLI: covariance-summary subcommand."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import csv
|
|
7
|
+
import json
|
|
8
|
+
from io import StringIO
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def register(subparsers: argparse._SubParsersAction) -> None: # type: ignore[type-arg]
|
|
13
|
+
p = subparsers.add_parser(
|
|
14
|
+
"covariance-summary",
|
|
15
|
+
help="Estimate covariance array memory from partition row counts.",
|
|
16
|
+
)
|
|
17
|
+
p.add_argument(
|
|
18
|
+
"--dataset-path",
|
|
19
|
+
required=True,
|
|
20
|
+
type=Path,
|
|
21
|
+
metavar="PATH",
|
|
22
|
+
help="Root directory of the covariance matrix dataset.",
|
|
23
|
+
)
|
|
24
|
+
p.add_argument(
|
|
25
|
+
"--name",
|
|
26
|
+
required=True,
|
|
27
|
+
metavar="TEXT",
|
|
28
|
+
help="Chromosome name (e.g. chr2).",
|
|
29
|
+
)
|
|
30
|
+
p.add_argument(
|
|
31
|
+
"--snp-first",
|
|
32
|
+
type=int,
|
|
33
|
+
default=-1,
|
|
34
|
+
metavar="INT",
|
|
35
|
+
help="First SNP position (auto-detected if omitted).",
|
|
36
|
+
)
|
|
37
|
+
p.add_argument(
|
|
38
|
+
"--snp-last",
|
|
39
|
+
type=int,
|
|
40
|
+
default=-1,
|
|
41
|
+
metavar="INT",
|
|
42
|
+
help="Last SNP position (auto-detected if omitted).",
|
|
43
|
+
)
|
|
44
|
+
p.add_argument(
|
|
45
|
+
"--output",
|
|
46
|
+
type=Path,
|
|
47
|
+
default=None,
|
|
48
|
+
metavar="PATH",
|
|
49
|
+
help="Write summary to a file instead of stdout.",
|
|
50
|
+
)
|
|
51
|
+
p.add_argument(
|
|
52
|
+
"--format",
|
|
53
|
+
choices=["tsv", "json"],
|
|
54
|
+
default="tsv",
|
|
55
|
+
help="Output format (default: tsv).",
|
|
56
|
+
)
|
|
57
|
+
p.set_defaults(func=_run)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _run(args: argparse.Namespace) -> int:
|
|
61
|
+
from ldetect_lite._util.covariance_summary import (
|
|
62
|
+
SUMMARY_COLUMNS,
|
|
63
|
+
summarize_covariance,
|
|
64
|
+
)
|
|
65
|
+
from ldetect_lite.io.partitions import CovarianceStore
|
|
66
|
+
|
|
67
|
+
store = CovarianceStore(root=args.dataset_path)
|
|
68
|
+
partitions, total = summarize_covariance(
|
|
69
|
+
args.name,
|
|
70
|
+
store,
|
|
71
|
+
snp_first=args.snp_first,
|
|
72
|
+
snp_last=args.snp_last,
|
|
73
|
+
)
|
|
74
|
+
rows = [summary.as_dict() for summary in partitions]
|
|
75
|
+
rows.append(total.as_dict())
|
|
76
|
+
|
|
77
|
+
if args.format == "json":
|
|
78
|
+
text = json.dumps({"partitions": rows[:-1], "total": rows[-1]}, indent=2)
|
|
79
|
+
else:
|
|
80
|
+
text = _format_tsv(rows, SUMMARY_COLUMNS)
|
|
81
|
+
|
|
82
|
+
if args.output is None:
|
|
83
|
+
print(text)
|
|
84
|
+
else:
|
|
85
|
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
86
|
+
args.output.write_text(text + "\n")
|
|
87
|
+
return 0
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _format_tsv(rows: list[dict[str, str | int | float]], columns: list[str]) -> str:
|
|
91
|
+
buffer = StringIO()
|
|
92
|
+
writer = csv.DictWriter(buffer, fieldnames=columns, delimiter="\t")
|
|
93
|
+
writer.writeheader()
|
|
94
|
+
writer.writerows(rows)
|
|
95
|
+
return buffer.getvalue().rstrip("\n")
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""CLI: extract-bpoints subcommand."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
_VALID_SUBSETS = ("fourier", "fourier_ls", "uniform", "uniform_ls")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def register(subparsers: argparse._SubParsersAction) -> None: # type: ignore[type-arg]
|
|
13
|
+
p = subparsers.add_parser(
|
|
14
|
+
"extract-bpoints",
|
|
15
|
+
help="Extract breakpoints from a find-minima JSON file and write a BED file.",
|
|
16
|
+
)
|
|
17
|
+
p.add_argument(
|
|
18
|
+
"--name", required=True, metavar="TEXT", help="Chromosome name (e.g. chr2)."
|
|
19
|
+
)
|
|
20
|
+
p.add_argument(
|
|
21
|
+
"--dataset-path",
|
|
22
|
+
required=True,
|
|
23
|
+
type=Path,
|
|
24
|
+
metavar="PATH",
|
|
25
|
+
help="Root directory of the covariance matrix dataset.",
|
|
26
|
+
)
|
|
27
|
+
p.add_argument(
|
|
28
|
+
"--breakpoints",
|
|
29
|
+
required=True,
|
|
30
|
+
type=Path,
|
|
31
|
+
metavar="PATH",
|
|
32
|
+
help="JSON file from find-minima.",
|
|
33
|
+
)
|
|
34
|
+
p.add_argument(
|
|
35
|
+
"--subset",
|
|
36
|
+
required=True,
|
|
37
|
+
choices=_VALID_SUBSETS,
|
|
38
|
+
metavar="SUBSET",
|
|
39
|
+
help=f"Breakpoint set to extract: {{{', '.join(_VALID_SUBSETS)}}}.",
|
|
40
|
+
)
|
|
41
|
+
p.add_argument(
|
|
42
|
+
"--output",
|
|
43
|
+
type=Path,
|
|
44
|
+
default=None,
|
|
45
|
+
metavar="PATH",
|
|
46
|
+
help="Output BED file (default: stdout).",
|
|
47
|
+
)
|
|
48
|
+
p.set_defaults(func=_run)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _run(args: argparse.Namespace) -> int:
|
|
52
|
+
from ldetect_lite.io.bed import write_bed
|
|
53
|
+
from ldetect_lite.io.partitions import CovarianceStore, read_partitions
|
|
54
|
+
|
|
55
|
+
store = CovarianceStore(root=args.dataset_path)
|
|
56
|
+
partitions = read_partitions(args.name, store)
|
|
57
|
+
snp_first = partitions[0][0]
|
|
58
|
+
snp_last = partitions[-1][1]
|
|
59
|
+
|
|
60
|
+
data = json.loads(args.breakpoints.read_text())
|
|
61
|
+
loci: list[int] = data[args.subset]["loci"]
|
|
62
|
+
|
|
63
|
+
write_bed(
|
|
64
|
+
name=args.name,
|
|
65
|
+
loci=loci,
|
|
66
|
+
snp_first=snp_first,
|
|
67
|
+
snp_last=snp_last,
|
|
68
|
+
output=args.output,
|
|
69
|
+
)
|
|
70
|
+
return 0
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""CLI: find-minima subcommand."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
_VALID_SUBSETS = ("fourier", "fourier_ls", "uniform", "uniform_ls")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def register(subparsers: argparse._SubParsersAction) -> None: # type: ignore[type-arg]
|
|
12
|
+
p = subparsers.add_parser(
|
|
13
|
+
"find-minima",
|
|
14
|
+
help="Detect LD block breakpoints via Hanning filter + local search.",
|
|
15
|
+
)
|
|
16
|
+
p.add_argument(
|
|
17
|
+
"--input",
|
|
18
|
+
required=True,
|
|
19
|
+
type=Path,
|
|
20
|
+
metavar="PATH",
|
|
21
|
+
help="Gzipped vector file from matrix-to-vector.",
|
|
22
|
+
)
|
|
23
|
+
p.add_argument(
|
|
24
|
+
"--chr-name", required=True, metavar="TEXT", help="Chromosome name (e.g. chr2)."
|
|
25
|
+
)
|
|
26
|
+
p.add_argument(
|
|
27
|
+
"--dataset-path",
|
|
28
|
+
required=True,
|
|
29
|
+
type=Path,
|
|
30
|
+
metavar="PATH",
|
|
31
|
+
help="Root directory of the covariance matrix dataset.",
|
|
32
|
+
)
|
|
33
|
+
p.add_argument(
|
|
34
|
+
"--n-snps-bw-bpoints",
|
|
35
|
+
required=True,
|
|
36
|
+
type=int,
|
|
37
|
+
metavar="N",
|
|
38
|
+
help="Target mean SNPs between breakpoints (default: 10000).",
|
|
39
|
+
)
|
|
40
|
+
p.add_argument(
|
|
41
|
+
"--output", required=True, type=Path, metavar="PATH", help="JSON output file."
|
|
42
|
+
)
|
|
43
|
+
p.add_argument(
|
|
44
|
+
"--snp-first",
|
|
45
|
+
type=int,
|
|
46
|
+
default=-1,
|
|
47
|
+
metavar="INT",
|
|
48
|
+
help="First SNP position (auto-detected if omitted).",
|
|
49
|
+
)
|
|
50
|
+
p.add_argument(
|
|
51
|
+
"--snp-last",
|
|
52
|
+
type=int,
|
|
53
|
+
default=-1,
|
|
54
|
+
metavar="INT",
|
|
55
|
+
help="Last SNP position (auto-detected if omitted).",
|
|
56
|
+
)
|
|
57
|
+
p.add_argument(
|
|
58
|
+
"--trackback-delta",
|
|
59
|
+
type=int,
|
|
60
|
+
default=200,
|
|
61
|
+
metavar="INT",
|
|
62
|
+
help="Coarse trackback search range (default: 200).",
|
|
63
|
+
)
|
|
64
|
+
p.add_argument(
|
|
65
|
+
"--trackback-step",
|
|
66
|
+
type=int,
|
|
67
|
+
default=20,
|
|
68
|
+
metavar="INT",
|
|
69
|
+
help="Coarse trackback step size (default: 20).",
|
|
70
|
+
)
|
|
71
|
+
p.add_argument(
|
|
72
|
+
"--init-search-loc",
|
|
73
|
+
type=int,
|
|
74
|
+
default=1000,
|
|
75
|
+
metavar="INT",
|
|
76
|
+
help="Starting width for exponential search (default: 1000).",
|
|
77
|
+
)
|
|
78
|
+
p.add_argument(
|
|
79
|
+
"--workers",
|
|
80
|
+
type=int,
|
|
81
|
+
default=1,
|
|
82
|
+
metavar="N",
|
|
83
|
+
help=(
|
|
84
|
+
"Parallel workers for local search. Higher values may multiply "
|
|
85
|
+
"memory use because each worker loads its own covariance window "
|
|
86
|
+
"(default: 1)."
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
p.add_argument(
|
|
90
|
+
"--metric-workers",
|
|
91
|
+
type=int,
|
|
92
|
+
default=None,
|
|
93
|
+
metavar="N",
|
|
94
|
+
help=(
|
|
95
|
+
"Parallel workers for streaming metric row passes "
|
|
96
|
+
"(default: inherit --workers)."
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
p.add_argument(
|
|
100
|
+
"--high-precision",
|
|
101
|
+
action="store_true",
|
|
102
|
+
help="Use 50-digit Decimal arithmetic for local search (slower).",
|
|
103
|
+
)
|
|
104
|
+
p.add_argument(
|
|
105
|
+
"--n-bpoints",
|
|
106
|
+
type=int,
|
|
107
|
+
default=None,
|
|
108
|
+
metavar="N",
|
|
109
|
+
help="Direct target breakpoint count (overrides --n-snps-bw-bpoints).",
|
|
110
|
+
)
|
|
111
|
+
p.add_argument(
|
|
112
|
+
"--subset",
|
|
113
|
+
choices=_VALID_SUBSETS,
|
|
114
|
+
action="append",
|
|
115
|
+
default=None,
|
|
116
|
+
metavar="SUBSET",
|
|
117
|
+
help=(
|
|
118
|
+
"Breakpoint subset to compute. Repeat to compute multiple subsets. "
|
|
119
|
+
"By default, all subsets are computed for backward compatibility."
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
p.set_defaults(func=_run)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _run(args: argparse.Namespace) -> int:
|
|
126
|
+
from ldetect_lite._cli.cmd_run import _resolve_workers
|
|
127
|
+
from ldetect_lite.io.partitions import CovarianceStore
|
|
128
|
+
from ldetect_lite.pipeline import find_breakpoints
|
|
129
|
+
|
|
130
|
+
metric_workers = _resolve_workers(args.metric_workers, args.workers)
|
|
131
|
+
|
|
132
|
+
store = CovarianceStore(root=args.dataset_path)
|
|
133
|
+
find_breakpoints(
|
|
134
|
+
input_path=args.input,
|
|
135
|
+
chr_name=args.chr_name,
|
|
136
|
+
store=store,
|
|
137
|
+
n_snps_bw_bpoints=args.n_snps_bw_bpoints,
|
|
138
|
+
output_path=args.output,
|
|
139
|
+
snp_first=args.snp_first,
|
|
140
|
+
snp_last=args.snp_last,
|
|
141
|
+
trackback_delta=args.trackback_delta,
|
|
142
|
+
trackback_step=args.trackback_step,
|
|
143
|
+
init_search_location=args.init_search_loc,
|
|
144
|
+
workers=args.workers,
|
|
145
|
+
metric_workers=metric_workers,
|
|
146
|
+
use_decimal=args.high_precision,
|
|
147
|
+
n_bpoints=args.n_bpoints,
|
|
148
|
+
subsets=set(args.subset) if args.subset else None,
|
|
149
|
+
)
|
|
150
|
+
return 0
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""CLI: interpolate-maps subcommand."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def register(subparsers: argparse._SubParsersAction) -> None: # type: ignore[type-arg]
|
|
10
|
+
p = subparsers.add_parser(
|
|
11
|
+
"interpolate-maps",
|
|
12
|
+
help="Interpolate genetic map positions onto SNP physical positions.",
|
|
13
|
+
)
|
|
14
|
+
p.add_argument(
|
|
15
|
+
"--snp-file",
|
|
16
|
+
required=True,
|
|
17
|
+
type=Path,
|
|
18
|
+
metavar="PATH",
|
|
19
|
+
help="BED file of SNP positions (columns: chrom start end rs_id).",
|
|
20
|
+
)
|
|
21
|
+
p.add_argument(
|
|
22
|
+
"--genetic-map",
|
|
23
|
+
required=True,
|
|
24
|
+
type=Path,
|
|
25
|
+
metavar="PATH",
|
|
26
|
+
help="Gzipped recombination map (position, rate, cM).",
|
|
27
|
+
)
|
|
28
|
+
p.add_argument(
|
|
29
|
+
"--output",
|
|
30
|
+
required=True,
|
|
31
|
+
type=Path,
|
|
32
|
+
metavar="PATH",
|
|
33
|
+
help="Gzipped output file (rs_id, position, genetic_position).",
|
|
34
|
+
)
|
|
35
|
+
p.add_argument(
|
|
36
|
+
"--mode",
|
|
37
|
+
choices=("point", "interval"),
|
|
38
|
+
default="point",
|
|
39
|
+
help=(
|
|
40
|
+
"Interpolation algorithm. 'point' (default) treats the map as "
|
|
41
|
+
"discrete points and interpolates between the two bracketing "
|
|
42
|
+
"points. 'interval' treats each map row as the start of a "
|
|
43
|
+
"genomic interval with its own recombination rate, matching "
|
|
44
|
+
"MacDonald et al.'s R interpolation scripts — use this for "
|
|
45
|
+
"interval-rate maps such as the deCODE map converted by "
|
|
46
|
+
"convert_decode_map.py."
|
|
47
|
+
),
|
|
48
|
+
)
|
|
49
|
+
p.set_defaults(func=_run)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _run(args: argparse.Namespace) -> int:
|
|
53
|
+
from ldetect_lite.interpolate_maps import interpolate, interpolate_intervals
|
|
54
|
+
|
|
55
|
+
fn = interpolate if args.mode == "point" else interpolate_intervals
|
|
56
|
+
fn(
|
|
57
|
+
snp_file=args.snp_file,
|
|
58
|
+
genetic_map=args.genetic_map,
|
|
59
|
+
output=args.output,
|
|
60
|
+
)
|
|
61
|
+
return 0
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""CLI: matrix-to-vector subcommand."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def register(subparsers: argparse._SubParsersAction) -> None: # type: ignore[type-arg]
|
|
10
|
+
p = subparsers.add_parser(
|
|
11
|
+
"matrix-to-vector",
|
|
12
|
+
help="Convert covariance matrix partitions to a correlation-sum vector.",
|
|
13
|
+
)
|
|
14
|
+
p.add_argument(
|
|
15
|
+
"--dataset-path",
|
|
16
|
+
required=True,
|
|
17
|
+
type=Path,
|
|
18
|
+
metavar="PATH",
|
|
19
|
+
help="Root directory of the covariance matrix dataset.",
|
|
20
|
+
)
|
|
21
|
+
p.add_argument(
|
|
22
|
+
"--name", required=True, metavar="TEXT", help="Chromosome name (e.g. chr2)."
|
|
23
|
+
)
|
|
24
|
+
p.add_argument(
|
|
25
|
+
"--output",
|
|
26
|
+
required=True,
|
|
27
|
+
type=Path,
|
|
28
|
+
metavar="PATH",
|
|
29
|
+
help="Gzipped output vector file (position, corr_sum).",
|
|
30
|
+
)
|
|
31
|
+
p.add_argument(
|
|
32
|
+
"--snp-first",
|
|
33
|
+
type=int,
|
|
34
|
+
default=-1,
|
|
35
|
+
metavar="INT",
|
|
36
|
+
help="First SNP position (auto-detected if omitted).",
|
|
37
|
+
)
|
|
38
|
+
p.add_argument(
|
|
39
|
+
"--snp-last",
|
|
40
|
+
type=int,
|
|
41
|
+
default=-1,
|
|
42
|
+
metavar="INT",
|
|
43
|
+
help="Last SNP position (auto-detected if omitted).",
|
|
44
|
+
)
|
|
45
|
+
p.add_argument(
|
|
46
|
+
"--mode",
|
|
47
|
+
choices=["diag", "vert"],
|
|
48
|
+
default="diag",
|
|
49
|
+
help="Calculation mode (default: diag).",
|
|
50
|
+
)
|
|
51
|
+
p.add_argument(
|
|
52
|
+
"--generate-heatmap",
|
|
53
|
+
action="store_true",
|
|
54
|
+
help="Write a PNG heatmap alongside the output file.",
|
|
55
|
+
)
|
|
56
|
+
p.add_argument(
|
|
57
|
+
"--workers",
|
|
58
|
+
type=int,
|
|
59
|
+
default=1,
|
|
60
|
+
metavar="N",
|
|
61
|
+
help=(
|
|
62
|
+
"Parallel workers for HDF5 matrix-to-vector partition computation "
|
|
63
|
+
"(default: 1)."
|
|
64
|
+
),
|
|
65
|
+
)
|
|
66
|
+
p.set_defaults(func=_run)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _run(args: argparse.Namespace) -> int:
|
|
70
|
+
from ldetect_lite.io.partitions import CovarianceStore
|
|
71
|
+
from ldetect_lite.matrix_analysis import MatrixAnalysis
|
|
72
|
+
|
|
73
|
+
store = CovarianceStore(root=args.dataset_path)
|
|
74
|
+
analysis = MatrixAnalysis(
|
|
75
|
+
name=args.name,
|
|
76
|
+
store=store,
|
|
77
|
+
snp_first=args.snp_first,
|
|
78
|
+
snp_last=args.snp_last,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if args.mode == "diag":
|
|
82
|
+
if args.generate_heatmap:
|
|
83
|
+
# Full matrix needed for heatmap — use non-lean path
|
|
84
|
+
analysis.calc_diag()
|
|
85
|
+
analysis.write_output_to_file(args.output)
|
|
86
|
+
else:
|
|
87
|
+
analysis.calc_diag_lean(args.output, matrix_workers=args.workers)
|
|
88
|
+
else:
|
|
89
|
+
raise NotImplementedError("vert mode is deprecated; use diag")
|
|
90
|
+
|
|
91
|
+
if args.generate_heatmap:
|
|
92
|
+
img_path = args.output.with_suffix(".png")
|
|
93
|
+
analysis.generate_img(img_path)
|
|
94
|
+
|
|
95
|
+
return 0
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""CLI: partition-chromosome subcommand."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def register(subparsers: argparse._SubParsersAction) -> None: # type: ignore[type-arg]
|
|
10
|
+
p = subparsers.add_parser(
|
|
11
|
+
"partition-chromosome",
|
|
12
|
+
help="Split a chromosome into overlapping windows using a genetic map.",
|
|
13
|
+
)
|
|
14
|
+
p.add_argument(
|
|
15
|
+
"--genetic-map",
|
|
16
|
+
required=True,
|
|
17
|
+
type=Path,
|
|
18
|
+
metavar="PATH",
|
|
19
|
+
help="Gzipped genetic map (chr, position, cM).",
|
|
20
|
+
)
|
|
21
|
+
p.add_argument(
|
|
22
|
+
"--n-individuals",
|
|
23
|
+
required=True,
|
|
24
|
+
type=int,
|
|
25
|
+
metavar="N",
|
|
26
|
+
help="Number of individuals in the reference panel.",
|
|
27
|
+
)
|
|
28
|
+
p.add_argument(
|
|
29
|
+
"--output",
|
|
30
|
+
required=True,
|
|
31
|
+
type=Path,
|
|
32
|
+
metavar="PATH",
|
|
33
|
+
help="Output partition file (space-delimited start/end pairs).",
|
|
34
|
+
)
|
|
35
|
+
p.add_argument(
|
|
36
|
+
"--window-size",
|
|
37
|
+
type=int,
|
|
38
|
+
default=5000,
|
|
39
|
+
metavar="N",
|
|
40
|
+
help="Target SNPs per partition window (default: 5000).",
|
|
41
|
+
)
|
|
42
|
+
p.add_argument(
|
|
43
|
+
"--ne",
|
|
44
|
+
type=float,
|
|
45
|
+
default=11418.0,
|
|
46
|
+
metavar="FLOAT",
|
|
47
|
+
help="Effective population size (default: 11418.0).",
|
|
48
|
+
)
|
|
49
|
+
p.add_argument(
|
|
50
|
+
"--cutoff",
|
|
51
|
+
type=float,
|
|
52
|
+
default=1.5e-8,
|
|
53
|
+
metavar="FLOAT",
|
|
54
|
+
help="Recombination fraction threshold for window extension (default: 1.5e-8).",
|
|
55
|
+
)
|
|
56
|
+
p.set_defaults(func=_run)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _run(args: argparse.Namespace) -> int:
|
|
60
|
+
from ldetect_lite.shrinkage import partition_chromosome
|
|
61
|
+
|
|
62
|
+
partition_chromosome(
|
|
63
|
+
genetic_map_path=args.genetic_map,
|
|
64
|
+
n_individuals=args.n_individuals,
|
|
65
|
+
output_path=args.output,
|
|
66
|
+
window_size=args.window_size,
|
|
67
|
+
ne=args.ne,
|
|
68
|
+
cutoff=args.cutoff,
|
|
69
|
+
)
|
|
70
|
+
return 0
|