Add files using upload-large-folder tool
Browse files- LTA_openwebtext_dualt/logs/lm1b_classic_dirichlet_every1k_infer_watch/infer_lta_lm1b_classic_dirichlet_len256_gbs512_4gpu_10k_save1k_20260523_step_0016000_t1p45.log +20 -0
- LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/packaging/_elffile.py +108 -0
- LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/packaging/_manylinux.py +262 -0
- LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/packaging/_musllinux.py +85 -0
- LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/packaging/_parser.py +393 -0
- LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/packaging/dependency_groups.py +302 -0
- LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/packaging/pylock.py +905 -0
- LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/packaging/utils.py +296 -0
- LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/cohere_asr/feature_extraction_cohere_asr.py +374 -0
- LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/cohere_asr/processing_cohere_asr.py +188 -0
- LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/esm/openfold_utils/chunk_utils.py +398 -0
- LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/esm/openfold_utils/loss.py +104 -0
- LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/esm/openfold_utils/protein.py +330 -0
- LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/esm/openfold_utils/residue_constants.py +979 -0
- LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/esm/openfold_utils/rigid_utils.py +1243 -0
- LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/ovis2/modeling_ovis2.py +730 -0
- LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/textnet/__init__.py +29 -0
- LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/textnet/configuration_textnet.py +94 -0
- LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/textnet/image_processing_pil_textnet.py +134 -0
- LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/textnet/modeling_textnet.py +417 -0
LTA_openwebtext_dualt/logs/lm1b_classic_dirichlet_every1k_infer_watch/infer_lta_lm1b_classic_dirichlet_len256_gbs512_4gpu_10k_save1k_20260523_step_0016000_t1p45.log
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[watch-classic-1k] 2026-05-23_16:05:04 infer runs/lta_lm1b_classic_dirichlet_len256_gbs512_4gpu_10k_save1k_20260523/step_0016000.pt -> docs/lta_samples/metrics_20260523/lm1b_classic_dirichlet_len256_every1k_normal_steps_state_t1p45_c1024_n256/lta_lm1b_classic_dirichlet_len256_gbs512_4gpu_10k_save1k_20260523/step_0016000
|
| 2 |
+
[ckpt] runs/lta_lm1b_classic_dirichlet_len256_gbs512_4gpu_10k_save1k_20260523/step_0016000.pt step=16000
|
| 3 |
+
[decode] steps128_c1024_t1p45 generated 16/256
|
| 4 |
+
[decode] steps128_c1024_t1p45 generated 32/256
|
| 5 |
+
[decode] steps128_c1024_t1p45 generated 48/256
|
| 6 |
+
[decode] steps128_c1024_t1p45 generated 64/256
|
| 7 |
+
[decode] steps128_c1024_t1p45 generated 80/256
|
| 8 |
+
[decode] steps128_c1024_t1p45 generated 96/256
|
| 9 |
+
[decode] steps128_c1024_t1p45 generated 112/256
|
| 10 |
+
[decode] steps128_c1024_t1p45 generated 128/256
|
| 11 |
+
[decode] steps128_c1024_t1p45 generated 144/256
|
| 12 |
+
[decode] steps128_c1024_t1p45 generated 160/256
|
| 13 |
+
[decode] steps128_c1024_t1p45 generated 176/256
|
| 14 |
+
[decode] steps128_c1024_t1p45 generated 192/256
|
| 15 |
+
[decode] steps128_c1024_t1p45 generated 208/256
|
| 16 |
+
[decode] steps128_c1024_t1p45 generated 224/256
|
| 17 |
+
[decode] steps128_c1024_t1p45 generated 240/256
|
| 18 |
+
[decode] steps128_c1024_t1p45 generated 256/256
|
| 19 |
+
[summary] {"name": "steps128_c1024_t1p45", "step": 16000, "decode_steps": 128, "concentration_max": 1024.0, "raw_genppl": 13.810265002566238, "stripped_genppl": 13.59726076657564, "sample_entropy": 1.4121913855304282, "distinct_1": 0.0078125, "distinct_2": 0.028247549019607843, "top_token_mass": 0.387176513671875, "raw_kept": 256, "stripped_kept": 256}
|
| 20 |
+
[watch-classic-1k] 2026-05-23_16:07:59 done step_0016000
|
LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/packaging/_elffile.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
ELF file parser.
|
| 3 |
+
|
| 4 |
+
This provides a class ``ELFFile`` that parses an ELF executable in a similar
|
| 5 |
+
interface to ``ZipFile``. Only the read interface is implemented.
|
| 6 |
+
|
| 7 |
+
ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import enum
|
| 13 |
+
import os
|
| 14 |
+
import struct
|
| 15 |
+
from typing import IO
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class ELFInvalid(ValueError):
|
| 19 |
+
pass
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class EIClass(enum.IntEnum):
|
| 23 |
+
C32 = 1
|
| 24 |
+
C64 = 2
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class EIData(enum.IntEnum):
|
| 28 |
+
Lsb = 1
|
| 29 |
+
Msb = 2
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class EMachine(enum.IntEnum):
|
| 33 |
+
I386 = 3
|
| 34 |
+
S390 = 22
|
| 35 |
+
Arm = 40
|
| 36 |
+
X8664 = 62
|
| 37 |
+
AArc64 = 183
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class ELFFile:
|
| 41 |
+
"""
|
| 42 |
+
Representation of an ELF executable.
|
| 43 |
+
"""
|
| 44 |
+
|
| 45 |
+
def __init__(self, f: IO[bytes]) -> None:
|
| 46 |
+
self._f = f
|
| 47 |
+
|
| 48 |
+
try:
|
| 49 |
+
ident = self._read("16B")
|
| 50 |
+
except struct.error as e:
|
| 51 |
+
raise ELFInvalid("unable to parse identification") from e
|
| 52 |
+
magic = bytes(ident[:4])
|
| 53 |
+
if magic != b"\x7fELF":
|
| 54 |
+
raise ELFInvalid(f"invalid magic: {magic!r}")
|
| 55 |
+
|
| 56 |
+
self.capacity = ident[4] # Format for program header (bitness).
|
| 57 |
+
self.encoding = ident[5] # Data structure encoding (endianness).
|
| 58 |
+
|
| 59 |
+
try:
|
| 60 |
+
# e_fmt: Format for program header.
|
| 61 |
+
# p_fmt: Format for section header.
|
| 62 |
+
# p_idx: Indexes to find p_type, p_offset, and p_filesz.
|
| 63 |
+
e_fmt, self._p_fmt, self._p_idx = {
|
| 64 |
+
(1, 1): ("<HHIIIIIHHH", "<IIIIIIII", (0, 1, 4)), # 32-bit LSB.
|
| 65 |
+
(1, 2): (">HHIIIIIHHH", ">IIIIIIII", (0, 1, 4)), # 32-bit MSB.
|
| 66 |
+
(2, 1): ("<HHIQQQIHHH", "<IIQQQQQQ", (0, 2, 5)), # 64-bit LSB.
|
| 67 |
+
(2, 2): (">HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit MSB.
|
| 68 |
+
}[(self.capacity, self.encoding)]
|
| 69 |
+
except KeyError as e:
|
| 70 |
+
raise ELFInvalid(
|
| 71 |
+
f"unrecognized capacity ({self.capacity}) or encoding ({self.encoding})"
|
| 72 |
+
) from e
|
| 73 |
+
|
| 74 |
+
try:
|
| 75 |
+
(
|
| 76 |
+
_,
|
| 77 |
+
self.machine, # Architecture type.
|
| 78 |
+
_,
|
| 79 |
+
_,
|
| 80 |
+
self._e_phoff, # Offset of program header.
|
| 81 |
+
_,
|
| 82 |
+
self.flags, # Processor-specific flags.
|
| 83 |
+
_,
|
| 84 |
+
self._e_phentsize, # Size of section.
|
| 85 |
+
self._e_phnum, # Number of sections.
|
| 86 |
+
) = self._read(e_fmt)
|
| 87 |
+
except struct.error as e:
|
| 88 |
+
raise ELFInvalid("unable to parse machine and section information") from e
|
| 89 |
+
|
| 90 |
+
def _read(self, fmt: str) -> tuple[int, ...]:
|
| 91 |
+
return struct.unpack(fmt, self._f.read(struct.calcsize(fmt)))
|
| 92 |
+
|
| 93 |
+
@property
|
| 94 |
+
def interpreter(self) -> str | None:
|
| 95 |
+
"""
|
| 96 |
+
The path recorded in the ``PT_INTERP`` section header.
|
| 97 |
+
"""
|
| 98 |
+
for index in range(self._e_phnum):
|
| 99 |
+
self._f.seek(self._e_phoff + self._e_phentsize * index)
|
| 100 |
+
try:
|
| 101 |
+
data = self._read(self._p_fmt)
|
| 102 |
+
except struct.error:
|
| 103 |
+
continue
|
| 104 |
+
if data[self._p_idx[0]] != 3: # Not PT_INTERP.
|
| 105 |
+
continue
|
| 106 |
+
self._f.seek(data[self._p_idx[1]])
|
| 107 |
+
return os.fsdecode(self._f.read(data[self._p_idx[2]])).strip("\0")
|
| 108 |
+
return None
|
LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/packaging/_manylinux.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import collections
|
| 4 |
+
import contextlib
|
| 5 |
+
import functools
|
| 6 |
+
import os
|
| 7 |
+
import re
|
| 8 |
+
import sys
|
| 9 |
+
import warnings
|
| 10 |
+
from typing import Generator, Iterator, NamedTuple, Sequence
|
| 11 |
+
|
| 12 |
+
from ._elffile import EIClass, EIData, ELFFile, EMachine
|
| 13 |
+
|
| 14 |
+
EF_ARM_ABIMASK = 0xFF000000
|
| 15 |
+
EF_ARM_ABI_VER5 = 0x05000000
|
| 16 |
+
EF_ARM_ABI_FLOAT_HARD = 0x00000400
|
| 17 |
+
|
| 18 |
+
_ALLOWED_ARCHS = {
|
| 19 |
+
"x86_64",
|
| 20 |
+
"aarch64",
|
| 21 |
+
"ppc64",
|
| 22 |
+
"ppc64le",
|
| 23 |
+
"s390x",
|
| 24 |
+
"loongarch64",
|
| 25 |
+
"riscv64",
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# `os.PathLike` not a generic type until Python 3.9, so sticking with `str`
|
| 30 |
+
# as the type for `path` until then.
|
| 31 |
+
@contextlib.contextmanager
|
| 32 |
+
def _parse_elf(path: str) -> Generator[ELFFile | None, None, None]:
|
| 33 |
+
try:
|
| 34 |
+
with open(path, "rb") as f:
|
| 35 |
+
yield ELFFile(f)
|
| 36 |
+
except (OSError, TypeError, ValueError):
|
| 37 |
+
yield None
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _is_linux_armhf(executable: str) -> bool:
|
| 41 |
+
# hard-float ABI can be detected from the ELF header of the running
|
| 42 |
+
# process
|
| 43 |
+
# https://static.docs.arm.com/ihi0044/g/aaelf32.pdf
|
| 44 |
+
with _parse_elf(executable) as f:
|
| 45 |
+
return (
|
| 46 |
+
f is not None
|
| 47 |
+
and f.capacity == EIClass.C32
|
| 48 |
+
and f.encoding == EIData.Lsb
|
| 49 |
+
and f.machine == EMachine.Arm
|
| 50 |
+
and f.flags & EF_ARM_ABIMASK == EF_ARM_ABI_VER5
|
| 51 |
+
and f.flags & EF_ARM_ABI_FLOAT_HARD == EF_ARM_ABI_FLOAT_HARD
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def _is_linux_i686(executable: str) -> bool:
|
| 56 |
+
with _parse_elf(executable) as f:
|
| 57 |
+
return (
|
| 58 |
+
f is not None
|
| 59 |
+
and f.capacity == EIClass.C32
|
| 60 |
+
and f.encoding == EIData.Lsb
|
| 61 |
+
and f.machine == EMachine.I386
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def _have_compatible_abi(executable: str, archs: Sequence[str]) -> bool:
|
| 66 |
+
if "armv7l" in archs:
|
| 67 |
+
return _is_linux_armhf(executable)
|
| 68 |
+
if "i686" in archs:
|
| 69 |
+
return _is_linux_i686(executable)
|
| 70 |
+
return any(arch in _ALLOWED_ARCHS for arch in archs)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
# If glibc ever changes its major version, we need to know what the last
|
| 74 |
+
# minor version was, so we can build the complete list of all versions.
|
| 75 |
+
# For now, guess what the highest minor version might be, assume it will
|
| 76 |
+
# be 50 for testing. Once this actually happens, update the dictionary
|
| 77 |
+
# with the actual value.
|
| 78 |
+
_LAST_GLIBC_MINOR: dict[int, int] = collections.defaultdict(lambda: 50)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
class _GLibCVersion(NamedTuple):
|
| 82 |
+
major: int
|
| 83 |
+
minor: int
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def _glibc_version_string_confstr() -> str | None:
|
| 87 |
+
"""
|
| 88 |
+
Primary implementation of glibc_version_string using os.confstr.
|
| 89 |
+
"""
|
| 90 |
+
# os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
|
| 91 |
+
# to be broken or missing. This strategy is used in the standard library
|
| 92 |
+
# platform module.
|
| 93 |
+
# https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183
|
| 94 |
+
try:
|
| 95 |
+
# Should be a string like "glibc 2.17".
|
| 96 |
+
version_string: str | None = os.confstr("CS_GNU_LIBC_VERSION")
|
| 97 |
+
assert version_string is not None
|
| 98 |
+
_, version = version_string.rsplit()
|
| 99 |
+
except (AssertionError, AttributeError, OSError, ValueError):
|
| 100 |
+
# os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
|
| 101 |
+
return None
|
| 102 |
+
return version
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def _glibc_version_string_ctypes() -> str | None:
|
| 106 |
+
"""
|
| 107 |
+
Fallback implementation of glibc_version_string using ctypes.
|
| 108 |
+
"""
|
| 109 |
+
try:
|
| 110 |
+
import ctypes # noqa: PLC0415
|
| 111 |
+
except ImportError:
|
| 112 |
+
return None
|
| 113 |
+
|
| 114 |
+
# ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen
|
| 115 |
+
# manpage says, "If filename is NULL, then the returned handle is for the
|
| 116 |
+
# main program". This way we can let the linker do the work to figure out
|
| 117 |
+
# which libc our process is actually using.
|
| 118 |
+
#
|
| 119 |
+
# We must also handle the special case where the executable is not a
|
| 120 |
+
# dynamically linked executable. This can occur when using musl libc,
|
| 121 |
+
# for example. In this situation, dlopen() will error, leading to an
|
| 122 |
+
# OSError. Interestingly, at least in the case of musl, there is no
|
| 123 |
+
# errno set on the OSError. The single string argument used to construct
|
| 124 |
+
# OSError comes from libc itself and is therefore not portable to
|
| 125 |
+
# hard code here. In any case, failure to call dlopen() means we
|
| 126 |
+
# can proceed, so we bail on our attempt.
|
| 127 |
+
try:
|
| 128 |
+
process_namespace = ctypes.CDLL(None)
|
| 129 |
+
except OSError:
|
| 130 |
+
return None
|
| 131 |
+
|
| 132 |
+
try:
|
| 133 |
+
gnu_get_libc_version = process_namespace.gnu_get_libc_version
|
| 134 |
+
except AttributeError:
|
| 135 |
+
# Symbol doesn't exist -> therefore, we are not linked to
|
| 136 |
+
# glibc.
|
| 137 |
+
return None
|
| 138 |
+
|
| 139 |
+
# Call gnu_get_libc_version, which returns a string like "2.5"
|
| 140 |
+
gnu_get_libc_version.restype = ctypes.c_char_p
|
| 141 |
+
version_str: str = gnu_get_libc_version()
|
| 142 |
+
# py2 / py3 compatibility:
|
| 143 |
+
if not isinstance(version_str, str):
|
| 144 |
+
version_str = version_str.decode("ascii")
|
| 145 |
+
|
| 146 |
+
return version_str
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def _glibc_version_string() -> str | None:
|
| 150 |
+
"""Returns glibc version string, or None if not using glibc."""
|
| 151 |
+
return _glibc_version_string_confstr() or _glibc_version_string_ctypes()
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def _parse_glibc_version(version_str: str) -> _GLibCVersion:
|
| 155 |
+
"""Parse glibc version.
|
| 156 |
+
|
| 157 |
+
We use a regexp instead of str.split because we want to discard any
|
| 158 |
+
random junk that might come after the minor version -- this might happen
|
| 159 |
+
in patched/forked versions of glibc (e.g. Linaro's version of glibc
|
| 160 |
+
uses version strings like "2.20-2014.11"). See gh-3588.
|
| 161 |
+
"""
|
| 162 |
+
m = re.match(r"(?P<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str)
|
| 163 |
+
if not m:
|
| 164 |
+
warnings.warn(
|
| 165 |
+
f"Expected glibc version with 2 components major.minor, got: {version_str}",
|
| 166 |
+
RuntimeWarning,
|
| 167 |
+
stacklevel=2,
|
| 168 |
+
)
|
| 169 |
+
return _GLibCVersion(-1, -1)
|
| 170 |
+
return _GLibCVersion(int(m.group("major")), int(m.group("minor")))
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
@functools.lru_cache
|
| 174 |
+
def _get_glibc_version() -> _GLibCVersion:
|
| 175 |
+
version_str = _glibc_version_string()
|
| 176 |
+
if version_str is None:
|
| 177 |
+
return _GLibCVersion(-1, -1)
|
| 178 |
+
return _parse_glibc_version(version_str)
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
# From PEP 513, PEP 600
|
| 182 |
+
def _is_compatible(arch: str, version: _GLibCVersion) -> bool:
|
| 183 |
+
sys_glibc = _get_glibc_version()
|
| 184 |
+
if sys_glibc < version:
|
| 185 |
+
return False
|
| 186 |
+
# Check for presence of _manylinux module.
|
| 187 |
+
try:
|
| 188 |
+
import _manylinux # noqa: PLC0415
|
| 189 |
+
except ImportError:
|
| 190 |
+
return True
|
| 191 |
+
if hasattr(_manylinux, "manylinux_compatible"):
|
| 192 |
+
result = _manylinux.manylinux_compatible(version[0], version[1], arch)
|
| 193 |
+
if result is not None:
|
| 194 |
+
return bool(result)
|
| 195 |
+
return True
|
| 196 |
+
if version == _GLibCVersion(2, 5) and hasattr(_manylinux, "manylinux1_compatible"):
|
| 197 |
+
return bool(_manylinux.manylinux1_compatible)
|
| 198 |
+
if version == _GLibCVersion(2, 12) and hasattr(
|
| 199 |
+
_manylinux, "manylinux2010_compatible"
|
| 200 |
+
):
|
| 201 |
+
return bool(_manylinux.manylinux2010_compatible)
|
| 202 |
+
if version == _GLibCVersion(2, 17) and hasattr(
|
| 203 |
+
_manylinux, "manylinux2014_compatible"
|
| 204 |
+
):
|
| 205 |
+
return bool(_manylinux.manylinux2014_compatible)
|
| 206 |
+
return True
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
_LEGACY_MANYLINUX_MAP: dict[_GLibCVersion, str] = {
|
| 210 |
+
# CentOS 7 w/ glibc 2.17 (PEP 599)
|
| 211 |
+
_GLibCVersion(2, 17): "manylinux2014",
|
| 212 |
+
# CentOS 6 w/ glibc 2.12 (PEP 571)
|
| 213 |
+
_GLibCVersion(2, 12): "manylinux2010",
|
| 214 |
+
# CentOS 5 w/ glibc 2.5 (PEP 513)
|
| 215 |
+
_GLibCVersion(2, 5): "manylinux1",
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def platform_tags(archs: Sequence[str]) -> Iterator[str]:
|
| 220 |
+
"""Generate manylinux tags compatible to the current platform.
|
| 221 |
+
|
| 222 |
+
:param archs: Sequence of compatible architectures.
|
| 223 |
+
The first one shall be the closest to the actual architecture and be the part of
|
| 224 |
+
platform tag after the ``linux_`` prefix, e.g. ``x86_64``.
|
| 225 |
+
The ``linux_`` prefix is assumed as a prerequisite for the current platform to
|
| 226 |
+
be manylinux-compatible.
|
| 227 |
+
|
| 228 |
+
:returns: An iterator of compatible manylinux tags.
|
| 229 |
+
"""
|
| 230 |
+
if not _have_compatible_abi(sys.executable, archs):
|
| 231 |
+
return
|
| 232 |
+
# Oldest glibc to be supported regardless of architecture is (2, 17).
|
| 233 |
+
too_old_glibc2 = _GLibCVersion(2, 16)
|
| 234 |
+
if set(archs) & {"x86_64", "i686"}:
|
| 235 |
+
# On x86/i686 also oldest glibc to be supported is (2, 5).
|
| 236 |
+
too_old_glibc2 = _GLibCVersion(2, 4)
|
| 237 |
+
current_glibc = _GLibCVersion(*_get_glibc_version())
|
| 238 |
+
glibc_max_list = [current_glibc]
|
| 239 |
+
# We can assume compatibility across glibc major versions.
|
| 240 |
+
# https://sourceware.org/bugzilla/show_bug.cgi?id=24636
|
| 241 |
+
#
|
| 242 |
+
# Build a list of maximum glibc versions so that we can
|
| 243 |
+
# output the canonical list of all glibc from current_glibc
|
| 244 |
+
# down to too_old_glibc2, including all intermediary versions.
|
| 245 |
+
for glibc_major in range(current_glibc.major - 1, 1, -1):
|
| 246 |
+
glibc_minor = _LAST_GLIBC_MINOR[glibc_major]
|
| 247 |
+
glibc_max_list.append(_GLibCVersion(glibc_major, glibc_minor))
|
| 248 |
+
for arch in archs:
|
| 249 |
+
for glibc_max in glibc_max_list:
|
| 250 |
+
if glibc_max.major == too_old_glibc2.major:
|
| 251 |
+
min_minor = too_old_glibc2.minor
|
| 252 |
+
else:
|
| 253 |
+
# For other glibc major versions oldest supported is (x, 0).
|
| 254 |
+
min_minor = -1
|
| 255 |
+
for glibc_minor in range(glibc_max.minor, min_minor, -1):
|
| 256 |
+
glibc_version = _GLibCVersion(glibc_max.major, glibc_minor)
|
| 257 |
+
if _is_compatible(arch, glibc_version):
|
| 258 |
+
yield "manylinux_{}_{}_{}".format(*glibc_version, arch)
|
| 259 |
+
|
| 260 |
+
# Handle the legacy manylinux1, manylinux2010, manylinux2014 tags.
|
| 261 |
+
if legacy_tag := _LEGACY_MANYLINUX_MAP.get(glibc_version):
|
| 262 |
+
yield f"{legacy_tag}_{arch}"
|
LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/packaging/_musllinux.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""PEP 656 support.
|
| 2 |
+
|
| 3 |
+
This module implements logic to detect if the currently running Python is
|
| 4 |
+
linked against musl, and what musl version is used.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import functools
|
| 10 |
+
import re
|
| 11 |
+
import subprocess
|
| 12 |
+
import sys
|
| 13 |
+
from typing import Iterator, NamedTuple, Sequence
|
| 14 |
+
|
| 15 |
+
from ._elffile import ELFFile
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class _MuslVersion(NamedTuple):
|
| 19 |
+
major: int
|
| 20 |
+
minor: int
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def _parse_musl_version(output: str) -> _MuslVersion | None:
|
| 24 |
+
lines = [n for n in (n.strip() for n in output.splitlines()) if n]
|
| 25 |
+
if len(lines) < 2 or lines[0][:4] != "musl":
|
| 26 |
+
return None
|
| 27 |
+
m = re.match(r"Version (\d+)\.(\d+)", lines[1])
|
| 28 |
+
if not m:
|
| 29 |
+
return None
|
| 30 |
+
return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2)))
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
@functools.lru_cache
|
| 34 |
+
def _get_musl_version(executable: str) -> _MuslVersion | None:
|
| 35 |
+
"""Detect currently-running musl runtime version.
|
| 36 |
+
|
| 37 |
+
This is done by checking the specified executable's dynamic linking
|
| 38 |
+
information, and invoking the loader to parse its output for a version
|
| 39 |
+
string. If the loader is musl, the output would be something like::
|
| 40 |
+
|
| 41 |
+
musl libc (x86_64)
|
| 42 |
+
Version 1.2.2
|
| 43 |
+
Dynamic Program Loader
|
| 44 |
+
"""
|
| 45 |
+
try:
|
| 46 |
+
with open(executable, "rb") as f:
|
| 47 |
+
ld = ELFFile(f).interpreter
|
| 48 |
+
except (OSError, TypeError, ValueError):
|
| 49 |
+
return None
|
| 50 |
+
if ld is None or "musl" not in ld:
|
| 51 |
+
return None
|
| 52 |
+
proc = subprocess.run([ld], check=False, stderr=subprocess.PIPE, text=True)
|
| 53 |
+
return _parse_musl_version(proc.stderr)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def platform_tags(archs: Sequence[str]) -> Iterator[str]:
|
| 57 |
+
"""Generate musllinux tags compatible to the current platform.
|
| 58 |
+
|
| 59 |
+
:param archs: Sequence of compatible architectures.
|
| 60 |
+
The first one shall be the closest to the actual architecture and be the part of
|
| 61 |
+
platform tag after the ``linux_`` prefix, e.g. ``x86_64``.
|
| 62 |
+
The ``linux_`` prefix is assumed as a prerequisite for the current platform to
|
| 63 |
+
be musllinux-compatible.
|
| 64 |
+
|
| 65 |
+
:returns: An iterator of compatible musllinux tags.
|
| 66 |
+
"""
|
| 67 |
+
sys_musl = _get_musl_version(sys.executable)
|
| 68 |
+
if sys_musl is None: # Python not dynamically linked against musl.
|
| 69 |
+
return
|
| 70 |
+
for arch in archs:
|
| 71 |
+
for minor in range(sys_musl.minor, -1, -1):
|
| 72 |
+
yield f"musllinux_{sys_musl.major}_{minor}_{arch}"
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
if __name__ == "__main__": # pragma: no cover
|
| 76 |
+
import sysconfig
|
| 77 |
+
|
| 78 |
+
plat = sysconfig.get_platform()
|
| 79 |
+
assert plat.startswith("linux-"), "not linux"
|
| 80 |
+
|
| 81 |
+
print("plat:", plat)
|
| 82 |
+
print("musl:", _get_musl_version(sys.executable))
|
| 83 |
+
print("tags:", end=" ")
|
| 84 |
+
for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])):
|
| 85 |
+
print(t, end="\n ")
|
LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/packaging/_parser.py
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Handwritten parser of dependency specifiers.
|
| 2 |
+
|
| 3 |
+
The docstring for each __parse_* function contains EBNF-inspired grammar representing
|
| 4 |
+
the implementation.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import ast
|
| 10 |
+
from typing import List, Literal, NamedTuple, Sequence, Tuple, Union
|
| 11 |
+
|
| 12 |
+
from ._tokenizer import DEFAULT_RULES, Tokenizer
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class Node:
|
| 16 |
+
__slots__ = ("value",)
|
| 17 |
+
|
| 18 |
+
def __init__(self, value: str) -> None:
|
| 19 |
+
self.value = value
|
| 20 |
+
|
| 21 |
+
def __str__(self) -> str:
|
| 22 |
+
return self.value
|
| 23 |
+
|
| 24 |
+
def __repr__(self) -> str:
|
| 25 |
+
return f"<{self.__class__.__name__}({self.value!r})>"
|
| 26 |
+
|
| 27 |
+
def serialize(self) -> str:
|
| 28 |
+
raise NotImplementedError
|
| 29 |
+
|
| 30 |
+
def __getstate__(self) -> str:
|
| 31 |
+
# Return just the value string for compactness and stability.
|
| 32 |
+
return self.value
|
| 33 |
+
|
| 34 |
+
def _restore_value(self, value: object) -> None:
|
| 35 |
+
if not isinstance(value, str):
|
| 36 |
+
raise TypeError(
|
| 37 |
+
f"Cannot restore {self.__class__.__name__} value from {value!r}"
|
| 38 |
+
)
|
| 39 |
+
self.value = value
|
| 40 |
+
|
| 41 |
+
def __setstate__(self, state: object) -> None:
|
| 42 |
+
if isinstance(state, str):
|
| 43 |
+
# New format (26.2+): just the value string.
|
| 44 |
+
self._restore_value(state)
|
| 45 |
+
return
|
| 46 |
+
if isinstance(state, tuple) and len(state) == 2:
|
| 47 |
+
# Old format (packaging <= 26.0, __slots__): (None, {slot: value}).
|
| 48 |
+
_, slot_dict = state
|
| 49 |
+
if isinstance(slot_dict, dict) and "value" in slot_dict:
|
| 50 |
+
self._restore_value(slot_dict["value"])
|
| 51 |
+
return
|
| 52 |
+
if isinstance(state, dict) and "value" in state:
|
| 53 |
+
# Old format (packaging <= 25.0, no __slots__): plain __dict__.
|
| 54 |
+
self._restore_value(state["value"])
|
| 55 |
+
return
|
| 56 |
+
raise TypeError(f"Cannot restore {self.__class__.__name__} from {state!r}")
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class Variable(Node):
|
| 60 |
+
__slots__ = ()
|
| 61 |
+
|
| 62 |
+
def serialize(self) -> str:
|
| 63 |
+
return str(self)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class Value(Node):
|
| 67 |
+
__slots__ = ()
|
| 68 |
+
|
| 69 |
+
def serialize(self) -> str:
|
| 70 |
+
return f'"{self}"'
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
class Op(Node):
|
| 74 |
+
__slots__ = ()
|
| 75 |
+
|
| 76 |
+
def serialize(self) -> str:
|
| 77 |
+
return str(self)
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
MarkerLogical = Literal["and", "or"]
|
| 81 |
+
MarkerVar = Union[Variable, Value]
|
| 82 |
+
MarkerItem = Tuple[MarkerVar, Op, MarkerVar]
|
| 83 |
+
MarkerAtom = Union[MarkerItem, Sequence["MarkerAtom"]]
|
| 84 |
+
MarkerList = List[Union["MarkerList", MarkerAtom, MarkerLogical]]
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
class ParsedRequirement(NamedTuple):
|
| 88 |
+
name: str
|
| 89 |
+
url: str
|
| 90 |
+
extras: list[str]
|
| 91 |
+
specifier: str
|
| 92 |
+
marker: MarkerList | None
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
# --------------------------------------------------------------------------------------
|
| 96 |
+
# Recursive descent parser for dependency specifier
|
| 97 |
+
# --------------------------------------------------------------------------------------
|
| 98 |
+
def parse_requirement(source: str) -> ParsedRequirement:
|
| 99 |
+
return _parse_requirement(Tokenizer(source, rules=DEFAULT_RULES))
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def _parse_requirement(tokenizer: Tokenizer) -> ParsedRequirement:
|
| 103 |
+
"""
|
| 104 |
+
requirement = WS? IDENTIFIER WS? extras WS? requirement_details
|
| 105 |
+
"""
|
| 106 |
+
tokenizer.consume("WS")
|
| 107 |
+
|
| 108 |
+
name_token = tokenizer.expect(
|
| 109 |
+
"IDENTIFIER", expected="package name at the start of dependency specifier"
|
| 110 |
+
)
|
| 111 |
+
name = name_token.text
|
| 112 |
+
tokenizer.consume("WS")
|
| 113 |
+
|
| 114 |
+
extras = _parse_extras(tokenizer)
|
| 115 |
+
tokenizer.consume("WS")
|
| 116 |
+
|
| 117 |
+
url, specifier, marker = _parse_requirement_details(tokenizer)
|
| 118 |
+
tokenizer.expect("END", expected="end of dependency specifier")
|
| 119 |
+
|
| 120 |
+
return ParsedRequirement(name, url, extras, specifier, marker)
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def _parse_requirement_details(
|
| 124 |
+
tokenizer: Tokenizer,
|
| 125 |
+
) -> tuple[str, str, MarkerList | None]:
|
| 126 |
+
"""
|
| 127 |
+
requirement_details = AT URL (WS requirement_marker?)?
|
| 128 |
+
| specifier WS? (requirement_marker)?
|
| 129 |
+
"""
|
| 130 |
+
|
| 131 |
+
specifier = ""
|
| 132 |
+
url = ""
|
| 133 |
+
marker = None
|
| 134 |
+
|
| 135 |
+
if tokenizer.check("AT"):
|
| 136 |
+
tokenizer.read()
|
| 137 |
+
tokenizer.consume("WS")
|
| 138 |
+
|
| 139 |
+
url_start = tokenizer.position
|
| 140 |
+
url = tokenizer.expect("URL", expected="URL after @").text
|
| 141 |
+
if tokenizer.check("END", peek=True):
|
| 142 |
+
return (url, specifier, marker)
|
| 143 |
+
|
| 144 |
+
tokenizer.expect("WS", expected="whitespace after URL")
|
| 145 |
+
|
| 146 |
+
# The input might end after whitespace.
|
| 147 |
+
if tokenizer.check("END", peek=True):
|
| 148 |
+
return (url, specifier, marker)
|
| 149 |
+
|
| 150 |
+
marker = _parse_requirement_marker(
|
| 151 |
+
tokenizer,
|
| 152 |
+
span_start=url_start,
|
| 153 |
+
expected="semicolon (after URL and whitespace)",
|
| 154 |
+
)
|
| 155 |
+
else:
|
| 156 |
+
specifier_start = tokenizer.position
|
| 157 |
+
specifier = _parse_specifier(tokenizer)
|
| 158 |
+
tokenizer.consume("WS")
|
| 159 |
+
|
| 160 |
+
if tokenizer.check("END", peek=True):
|
| 161 |
+
return (url, specifier, marker)
|
| 162 |
+
|
| 163 |
+
marker = _parse_requirement_marker(
|
| 164 |
+
tokenizer,
|
| 165 |
+
span_start=specifier_start,
|
| 166 |
+
expected=(
|
| 167 |
+
"comma (within version specifier), semicolon (after version specifier)"
|
| 168 |
+
if specifier
|
| 169 |
+
else "semicolon (after name with no version specifier)"
|
| 170 |
+
),
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
return (url, specifier, marker)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def _parse_requirement_marker(
|
| 177 |
+
tokenizer: Tokenizer, *, span_start: int, expected: str
|
| 178 |
+
) -> MarkerList:
|
| 179 |
+
"""
|
| 180 |
+
requirement_marker = SEMICOLON marker WS?
|
| 181 |
+
"""
|
| 182 |
+
|
| 183 |
+
if not tokenizer.check("SEMICOLON"):
|
| 184 |
+
tokenizer.raise_syntax_error(
|
| 185 |
+
f"Expected {expected} or end",
|
| 186 |
+
span_start=span_start,
|
| 187 |
+
span_end=None,
|
| 188 |
+
)
|
| 189 |
+
tokenizer.read()
|
| 190 |
+
|
| 191 |
+
marker = _parse_marker(tokenizer)
|
| 192 |
+
tokenizer.consume("WS")
|
| 193 |
+
|
| 194 |
+
return marker
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def _parse_extras(tokenizer: Tokenizer) -> list[str]:
|
| 198 |
+
"""
|
| 199 |
+
extras = (LEFT_BRACKET wsp* extras_list? wsp* RIGHT_BRACKET)?
|
| 200 |
+
"""
|
| 201 |
+
if not tokenizer.check("LEFT_BRACKET", peek=True):
|
| 202 |
+
return []
|
| 203 |
+
|
| 204 |
+
with tokenizer.enclosing_tokens(
|
| 205 |
+
"LEFT_BRACKET",
|
| 206 |
+
"RIGHT_BRACKET",
|
| 207 |
+
around="extras",
|
| 208 |
+
):
|
| 209 |
+
tokenizer.consume("WS")
|
| 210 |
+
extras = _parse_extras_list(tokenizer)
|
| 211 |
+
tokenizer.consume("WS")
|
| 212 |
+
|
| 213 |
+
return extras
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def _parse_extras_list(tokenizer: Tokenizer) -> list[str]:
|
| 217 |
+
"""
|
| 218 |
+
extras_list = identifier (wsp* ',' wsp* identifier)*
|
| 219 |
+
"""
|
| 220 |
+
extras: list[str] = []
|
| 221 |
+
|
| 222 |
+
if not tokenizer.check("IDENTIFIER"):
|
| 223 |
+
return extras
|
| 224 |
+
|
| 225 |
+
extras.append(tokenizer.read().text)
|
| 226 |
+
|
| 227 |
+
while True:
|
| 228 |
+
tokenizer.consume("WS")
|
| 229 |
+
if tokenizer.check("IDENTIFIER", peek=True):
|
| 230 |
+
tokenizer.raise_syntax_error("Expected comma between extra names")
|
| 231 |
+
elif not tokenizer.check("COMMA"):
|
| 232 |
+
break
|
| 233 |
+
|
| 234 |
+
tokenizer.read()
|
| 235 |
+
tokenizer.consume("WS")
|
| 236 |
+
|
| 237 |
+
extra_token = tokenizer.expect("IDENTIFIER", expected="extra name after comma")
|
| 238 |
+
extras.append(extra_token.text)
|
| 239 |
+
|
| 240 |
+
return extras
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
def _parse_specifier(tokenizer: Tokenizer) -> str:
|
| 244 |
+
"""
|
| 245 |
+
specifier = LEFT_PARENTHESIS WS? version_many WS? RIGHT_PARENTHESIS
|
| 246 |
+
| WS? version_many WS?
|
| 247 |
+
"""
|
| 248 |
+
with tokenizer.enclosing_tokens(
|
| 249 |
+
"LEFT_PARENTHESIS",
|
| 250 |
+
"RIGHT_PARENTHESIS",
|
| 251 |
+
around="version specifier",
|
| 252 |
+
):
|
| 253 |
+
tokenizer.consume("WS")
|
| 254 |
+
parsed_specifiers = _parse_version_many(tokenizer)
|
| 255 |
+
tokenizer.consume("WS")
|
| 256 |
+
|
| 257 |
+
return parsed_specifiers
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
def _parse_version_many(tokenizer: Tokenizer) -> str:
|
| 261 |
+
"""
|
| 262 |
+
version_many = (SPECIFIER (WS? COMMA WS? SPECIFIER)*)?
|
| 263 |
+
"""
|
| 264 |
+
parsed_specifiers = ""
|
| 265 |
+
while tokenizer.check("SPECIFIER"):
|
| 266 |
+
span_start = tokenizer.position
|
| 267 |
+
parsed_specifiers += tokenizer.read().text
|
| 268 |
+
if tokenizer.check("VERSION_PREFIX_TRAIL", peek=True):
|
| 269 |
+
tokenizer.raise_syntax_error(
|
| 270 |
+
".* suffix can only be used with `==` or `!=` operators",
|
| 271 |
+
span_start=span_start,
|
| 272 |
+
span_end=tokenizer.position + 1,
|
| 273 |
+
)
|
| 274 |
+
if tokenizer.check("VERSION_LOCAL_LABEL_TRAIL", peek=True):
|
| 275 |
+
tokenizer.raise_syntax_error(
|
| 276 |
+
"Local version label can only be used with `==` or `!=` operators",
|
| 277 |
+
span_start=span_start,
|
| 278 |
+
span_end=tokenizer.position,
|
| 279 |
+
)
|
| 280 |
+
tokenizer.consume("WS")
|
| 281 |
+
if not tokenizer.check("COMMA"):
|
| 282 |
+
break
|
| 283 |
+
parsed_specifiers += tokenizer.read().text
|
| 284 |
+
tokenizer.consume("WS")
|
| 285 |
+
|
| 286 |
+
return parsed_specifiers
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
# --------------------------------------------------------------------------------------
|
| 290 |
+
# Recursive descent parser for marker expression
|
| 291 |
+
# --------------------------------------------------------------------------------------
|
| 292 |
+
def parse_marker(source: str) -> MarkerList:
|
| 293 |
+
return _parse_full_marker(Tokenizer(source, rules=DEFAULT_RULES))
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
def _parse_full_marker(tokenizer: Tokenizer) -> MarkerList:
|
| 297 |
+
retval = _parse_marker(tokenizer)
|
| 298 |
+
tokenizer.expect("END", expected="end of marker expression")
|
| 299 |
+
return retval
|
| 300 |
+
|
| 301 |
+
|
| 302 |
+
def _parse_marker(tokenizer: Tokenizer) -> MarkerList:
|
| 303 |
+
"""
|
| 304 |
+
marker = marker_atom (BOOLOP marker_atom)+
|
| 305 |
+
"""
|
| 306 |
+
expression = [_parse_marker_atom(tokenizer)]
|
| 307 |
+
while tokenizer.check("BOOLOP"):
|
| 308 |
+
token = tokenizer.read()
|
| 309 |
+
expr_right = _parse_marker_atom(tokenizer)
|
| 310 |
+
expression.extend((token.text, expr_right))
|
| 311 |
+
return expression
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
def _parse_marker_atom(tokenizer: Tokenizer) -> MarkerAtom:
|
| 315 |
+
"""
|
| 316 |
+
marker_atom = WS? LEFT_PARENTHESIS WS? marker WS? RIGHT_PARENTHESIS WS?
|
| 317 |
+
| WS? marker_item WS?
|
| 318 |
+
"""
|
| 319 |
+
|
| 320 |
+
tokenizer.consume("WS")
|
| 321 |
+
if tokenizer.check("LEFT_PARENTHESIS", peek=True):
|
| 322 |
+
with tokenizer.enclosing_tokens(
|
| 323 |
+
"LEFT_PARENTHESIS",
|
| 324 |
+
"RIGHT_PARENTHESIS",
|
| 325 |
+
around="marker expression",
|
| 326 |
+
):
|
| 327 |
+
tokenizer.consume("WS")
|
| 328 |
+
marker: MarkerAtom = _parse_marker(tokenizer)
|
| 329 |
+
tokenizer.consume("WS")
|
| 330 |
+
else:
|
| 331 |
+
marker = _parse_marker_item(tokenizer)
|
| 332 |
+
tokenizer.consume("WS")
|
| 333 |
+
return marker
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
def _parse_marker_item(tokenizer: Tokenizer) -> MarkerItem:
|
| 337 |
+
"""
|
| 338 |
+
marker_item = WS? marker_var WS? marker_op WS? marker_var WS?
|
| 339 |
+
"""
|
| 340 |
+
tokenizer.consume("WS")
|
| 341 |
+
marker_var_left = _parse_marker_var(tokenizer)
|
| 342 |
+
tokenizer.consume("WS")
|
| 343 |
+
marker_op = _parse_marker_op(tokenizer)
|
| 344 |
+
tokenizer.consume("WS")
|
| 345 |
+
marker_var_right = _parse_marker_var(tokenizer)
|
| 346 |
+
tokenizer.consume("WS")
|
| 347 |
+
return (marker_var_left, marker_op, marker_var_right)
|
| 348 |
+
|
| 349 |
+
|
| 350 |
+
def _parse_marker_var(tokenizer: Tokenizer) -> MarkerVar: # noqa: RET503
|
| 351 |
+
"""
|
| 352 |
+
marker_var = VARIABLE | QUOTED_STRING
|
| 353 |
+
"""
|
| 354 |
+
if tokenizer.check("VARIABLE"):
|
| 355 |
+
return process_env_var(tokenizer.read().text.replace(".", "_"))
|
| 356 |
+
elif tokenizer.check("QUOTED_STRING"):
|
| 357 |
+
return process_python_str(tokenizer.read().text)
|
| 358 |
+
else:
|
| 359 |
+
tokenizer.raise_syntax_error(
|
| 360 |
+
message="Expected a marker variable or quoted string"
|
| 361 |
+
)
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
def process_env_var(env_var: str) -> Variable:
|
| 365 |
+
if env_var in ("platform_python_implementation", "python_implementation"):
|
| 366 |
+
return Variable("platform_python_implementation")
|
| 367 |
+
else:
|
| 368 |
+
return Variable(env_var)
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
def process_python_str(python_str: str) -> Value:
|
| 372 |
+
value = ast.literal_eval(python_str)
|
| 373 |
+
return Value(str(value))
|
| 374 |
+
|
| 375 |
+
|
| 376 |
+
def _parse_marker_op(tokenizer: Tokenizer) -> Op:
|
| 377 |
+
"""
|
| 378 |
+
marker_op = IN | NOT IN | OP
|
| 379 |
+
"""
|
| 380 |
+
if tokenizer.check("IN"):
|
| 381 |
+
tokenizer.read()
|
| 382 |
+
return Op("in")
|
| 383 |
+
elif tokenizer.check("NOT"):
|
| 384 |
+
tokenizer.read()
|
| 385 |
+
tokenizer.expect("WS", expected="whitespace after 'not'")
|
| 386 |
+
tokenizer.expect("IN", expected="'in' after 'not'")
|
| 387 |
+
return Op("not in")
|
| 388 |
+
elif tokenizer.check("OP"):
|
| 389 |
+
return Op(tokenizer.read().text)
|
| 390 |
+
else:
|
| 391 |
+
return tokenizer.raise_syntax_error(
|
| 392 |
+
"Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in"
|
| 393 |
+
)
|
LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/packaging/dependency_groups.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import re
|
| 4 |
+
from collections.abc import Mapping, Sequence
|
| 5 |
+
|
| 6 |
+
from .errors import _ErrorCollector
|
| 7 |
+
from .requirements import Requirement
|
| 8 |
+
|
| 9 |
+
__all__ = [
|
| 10 |
+
"CyclicDependencyGroup",
|
| 11 |
+
"DependencyGroupInclude",
|
| 12 |
+
"DependencyGroupResolver",
|
| 13 |
+
"DuplicateGroupNames",
|
| 14 |
+
"InvalidDependencyGroupObject",
|
| 15 |
+
"resolve_dependency_groups",
|
| 16 |
+
]
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def __dir__() -> list[str]:
|
| 20 |
+
return __all__
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
# -----------
|
| 24 |
+
# Error Types
|
| 25 |
+
# -----------
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class DuplicateGroupNames(ValueError):
|
| 29 |
+
"""
|
| 30 |
+
The same dependency groups were defined twice, with different non-normalized names.
|
| 31 |
+
"""
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class CyclicDependencyGroup(ValueError):
|
| 35 |
+
"""
|
| 36 |
+
The dependency group includes form a cycle.
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
def __init__(self, requested_group: str, group: str, include_group: str) -> None:
|
| 40 |
+
self.requested_group = requested_group
|
| 41 |
+
self.group = group
|
| 42 |
+
self.include_group = include_group
|
| 43 |
+
|
| 44 |
+
if include_group == group:
|
| 45 |
+
reason = f"{group} includes itself"
|
| 46 |
+
else:
|
| 47 |
+
reason = f"{include_group} -> {group}, {group} -> {include_group}"
|
| 48 |
+
super().__init__(
|
| 49 |
+
"Cyclic dependency group include while resolving "
|
| 50 |
+
f"{requested_group}: {reason}"
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
# in the PEP 735 spec, the tables in dependency group lists were described as
|
| 55 |
+
# "Dependency Object Specifiers", but the only defined type of object was a
|
| 56 |
+
# "Dependency Group Include" -- hence the naming of this error as "Object"
|
| 57 |
+
class InvalidDependencyGroupObject(ValueError):
|
| 58 |
+
"""
|
| 59 |
+
A member of a dependency group was identified as a dict, but was not in a valid
|
| 60 |
+
format.
|
| 61 |
+
"""
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
# ------------------------
|
| 65 |
+
# Object Model & Interface
|
| 66 |
+
# ------------------------
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
class DependencyGroupInclude:
|
| 70 |
+
__slots__ = ("include_group",)
|
| 71 |
+
|
| 72 |
+
def __init__(self, include_group: str) -> None:
|
| 73 |
+
"""
|
| 74 |
+
Initialize a DependencyGroupInclude.
|
| 75 |
+
|
| 76 |
+
:param include_group: The name of the group referred to by this include.
|
| 77 |
+
"""
|
| 78 |
+
self.include_group = include_group
|
| 79 |
+
|
| 80 |
+
def __repr__(self) -> str:
|
| 81 |
+
return f"{self.__class__.__name__}({self.include_group!r})"
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
class DependencyGroupResolver:
|
| 85 |
+
"""
|
| 86 |
+
A resolver for Dependency Group data.
|
| 87 |
+
|
| 88 |
+
This class handles caching, name normalization, cycle detection, and other
|
| 89 |
+
parsing requirements. There are only two public methods for exploring the data:
|
| 90 |
+
``lookup()`` and ``resolve()``.
|
| 91 |
+
|
| 92 |
+
:param dependency_groups: A mapping, as provided via pyproject
|
| 93 |
+
``[dependency-groups]``.
|
| 94 |
+
"""
|
| 95 |
+
|
| 96 |
+
def __init__(
|
| 97 |
+
self,
|
| 98 |
+
dependency_groups: Mapping[str, Sequence[str | Mapping[str, str]]],
|
| 99 |
+
) -> None:
|
| 100 |
+
errors = _ErrorCollector()
|
| 101 |
+
|
| 102 |
+
self.dependency_groups = _normalize_group_names(dependency_groups, errors)
|
| 103 |
+
|
| 104 |
+
# a map of group names to parsed data
|
| 105 |
+
self._parsed_groups: dict[
|
| 106 |
+
str, tuple[Requirement | DependencyGroupInclude, ...]
|
| 107 |
+
] = {}
|
| 108 |
+
# a map of group names to their ancestors, used for cycle detection
|
| 109 |
+
self._include_graph_ancestors: dict[str, tuple[str, ...]] = {}
|
| 110 |
+
# a cache of completed resolutions to Requirement lists
|
| 111 |
+
self._resolve_cache: dict[str, tuple[Requirement, ...]] = {}
|
| 112 |
+
|
| 113 |
+
errors.finalize("[dependency-groups] data was invalid")
|
| 114 |
+
|
| 115 |
+
def lookup(self, group: str) -> tuple[Requirement | DependencyGroupInclude, ...]:
|
| 116 |
+
"""
|
| 117 |
+
Lookup a group name, returning the parsed dependency data for that group.
|
| 118 |
+
This will not resolve includes.
|
| 119 |
+
|
| 120 |
+
:param group: the name of the group to lookup
|
| 121 |
+
"""
|
| 122 |
+
group = _normalize_name(group)
|
| 123 |
+
|
| 124 |
+
with _ErrorCollector().on_exit(
|
| 125 |
+
f"[dependency-groups] data for {group!r} was malformed"
|
| 126 |
+
) as errors:
|
| 127 |
+
return self._parse_group(group, errors)
|
| 128 |
+
|
| 129 |
+
def resolve(self, group: str) -> tuple[Requirement, ...]:
|
| 130 |
+
"""
|
| 131 |
+
Resolve a dependency group to a list of requirements.
|
| 132 |
+
|
| 133 |
+
:param group: the name of the group to resolve
|
| 134 |
+
"""
|
| 135 |
+
group = _normalize_name(group)
|
| 136 |
+
|
| 137 |
+
with _ErrorCollector().on_exit(
|
| 138 |
+
f"[dependency-groups] data for {group!r} was malformed"
|
| 139 |
+
) as errors:
|
| 140 |
+
return self._resolve(group, group, errors)
|
| 141 |
+
|
| 142 |
+
def _resolve(
|
| 143 |
+
self, group: str, requested_group: str, errors: _ErrorCollector
|
| 144 |
+
) -> tuple[Requirement, ...]:
|
| 145 |
+
"""
|
| 146 |
+
This is a helper for cached resolution to strings. It preserves the name of the
|
| 147 |
+
group which the user initially requested in order to present a clearer error in
|
| 148 |
+
the event that a cycle is detected.
|
| 149 |
+
|
| 150 |
+
:param group: The normalized name of the group to resolve.
|
| 151 |
+
:param requested_group: The group which was used in the original, user-facing
|
| 152 |
+
request.
|
| 153 |
+
"""
|
| 154 |
+
if group in self._resolve_cache:
|
| 155 |
+
return self._resolve_cache[group]
|
| 156 |
+
|
| 157 |
+
parsed = self._parse_group(group, errors)
|
| 158 |
+
|
| 159 |
+
resolved_group = []
|
| 160 |
+
|
| 161 |
+
for item in parsed:
|
| 162 |
+
if isinstance(item, Requirement):
|
| 163 |
+
resolved_group.append(item)
|
| 164 |
+
elif isinstance(item, DependencyGroupInclude):
|
| 165 |
+
include_group = _normalize_name(item.include_group)
|
| 166 |
+
|
| 167 |
+
# if a group is cyclic, record the error
|
| 168 |
+
# otherwise, follow the include_group reference
|
| 169 |
+
#
|
| 170 |
+
# this allows us to examine all includes in a group, even in the
|
| 171 |
+
# presence of errors
|
| 172 |
+
if include_group in self._include_graph_ancestors.get(group, ()):
|
| 173 |
+
errors.error(
|
| 174 |
+
CyclicDependencyGroup(
|
| 175 |
+
requested_group, group, item.include_group
|
| 176 |
+
)
|
| 177 |
+
)
|
| 178 |
+
else:
|
| 179 |
+
self._include_graph_ancestors[include_group] = (
|
| 180 |
+
*self._include_graph_ancestors.get(group, ()),
|
| 181 |
+
group,
|
| 182 |
+
)
|
| 183 |
+
resolved_group.extend(
|
| 184 |
+
self._resolve(include_group, requested_group, errors)
|
| 185 |
+
)
|
| 186 |
+
else: # pragma: no cover
|
| 187 |
+
raise NotImplementedError(
|
| 188 |
+
f"Invalid dependency group item after parse: {item}"
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
# in the event that errors were detected, present the group as empty and do not
|
| 192 |
+
# cache the result
|
| 193 |
+
# this ensures that repeated access to a cyclic group will raise multiple errors
|
| 194 |
+
if errors.errors:
|
| 195 |
+
return ()
|
| 196 |
+
|
| 197 |
+
self._resolve_cache[group] = tuple(resolved_group)
|
| 198 |
+
return self._resolve_cache[group]
|
| 199 |
+
|
| 200 |
+
def _parse_group(
|
| 201 |
+
self, group: str, errors: _ErrorCollector
|
| 202 |
+
) -> tuple[Requirement | DependencyGroupInclude, ...]:
|
| 203 |
+
# short circuit -- never do the work twice
|
| 204 |
+
if group in self._parsed_groups:
|
| 205 |
+
return self._parsed_groups[group]
|
| 206 |
+
|
| 207 |
+
if group not in self.dependency_groups:
|
| 208 |
+
errors.error(LookupError(f"Dependency group '{group}' not found"))
|
| 209 |
+
return ()
|
| 210 |
+
|
| 211 |
+
raw_group = self.dependency_groups[group]
|
| 212 |
+
if isinstance(raw_group, str):
|
| 213 |
+
errors.error(
|
| 214 |
+
TypeError(
|
| 215 |
+
f"Dependency group {group!r} contained a string rather than a list."
|
| 216 |
+
)
|
| 217 |
+
)
|
| 218 |
+
return ()
|
| 219 |
+
|
| 220 |
+
if not isinstance(raw_group, Sequence):
|
| 221 |
+
errors.error(
|
| 222 |
+
TypeError(f"Dependency group {group!r} is not a sequence type.")
|
| 223 |
+
)
|
| 224 |
+
return ()
|
| 225 |
+
|
| 226 |
+
elements: list[Requirement | DependencyGroupInclude] = []
|
| 227 |
+
for item in raw_group:
|
| 228 |
+
if isinstance(item, str):
|
| 229 |
+
# packaging.requirements.Requirement parsing ensures that this is a
|
| 230 |
+
# valid PEP 508 Dependency Specifier
|
| 231 |
+
# raises InvalidRequirement on failure
|
| 232 |
+
elements.append(Requirement(item))
|
| 233 |
+
elif isinstance(item, Mapping):
|
| 234 |
+
if tuple(item.keys()) != ("include-group",):
|
| 235 |
+
errors.error(
|
| 236 |
+
InvalidDependencyGroupObject(
|
| 237 |
+
f"Invalid dependency group item: {item!r}"
|
| 238 |
+
)
|
| 239 |
+
)
|
| 240 |
+
else:
|
| 241 |
+
include_group = item["include-group"]
|
| 242 |
+
elements.append(DependencyGroupInclude(include_group=include_group))
|
| 243 |
+
else:
|
| 244 |
+
errors.error(TypeError(f"Invalid dependency group item: {item!r}"))
|
| 245 |
+
|
| 246 |
+
self._parsed_groups[group] = tuple(elements)
|
| 247 |
+
return self._parsed_groups[group]
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
# --------------------
|
| 251 |
+
# Functional Interface
|
| 252 |
+
# --------------------
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def resolve_dependency_groups(
|
| 256 |
+
dependency_groups: Mapping[str, Sequence[str | Mapping[str, str]]], /, *groups: str
|
| 257 |
+
) -> tuple[str, ...]:
|
| 258 |
+
"""
|
| 259 |
+
Resolve a dependency group to a tuple of requirements, as strings.
|
| 260 |
+
|
| 261 |
+
:param dependency_groups: the parsed contents of the ``[dependency-groups]`` table
|
| 262 |
+
from ``pyproject.toml``
|
| 263 |
+
:param groups: the name of the group(s) to resolve
|
| 264 |
+
"""
|
| 265 |
+
resolver = DependencyGroupResolver(dependency_groups)
|
| 266 |
+
return tuple(str(r) for group in groups for r in resolver.resolve(group))
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
# ----------------
|
| 270 |
+
# internal helpers
|
| 271 |
+
# ----------------
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
_NORMALIZE_PATTERN = re.compile(r"[-_.]+")
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def _normalize_name(name: str) -> str:
|
| 278 |
+
return _NORMALIZE_PATTERN.sub("-", name).lower()
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
def _normalize_group_names(
|
| 282 |
+
dependency_groups: Mapping[str, Sequence[str | Mapping[str, str]]],
|
| 283 |
+
errors: _ErrorCollector,
|
| 284 |
+
) -> dict[str, Sequence[str | Mapping[str, str]]]:
|
| 285 |
+
original_names: dict[str, list[str]] = {}
|
| 286 |
+
normalized_groups: dict[str, Sequence[str | Mapping[str, str]]] = {}
|
| 287 |
+
|
| 288 |
+
for group_name, value in dependency_groups.items():
|
| 289 |
+
normed_group_name = _normalize_name(group_name)
|
| 290 |
+
original_names.setdefault(normed_group_name, []).append(group_name)
|
| 291 |
+
normalized_groups[normed_group_name] = value
|
| 292 |
+
|
| 293 |
+
for normed_name, names in original_names.items():
|
| 294 |
+
if len(names) > 1:
|
| 295 |
+
errors.error(
|
| 296 |
+
DuplicateGroupNames(
|
| 297 |
+
"Duplicate dependency group names: "
|
| 298 |
+
f"{normed_name} ({', '.join(names)})"
|
| 299 |
+
)
|
| 300 |
+
)
|
| 301 |
+
|
| 302 |
+
return normalized_groups
|
LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/packaging/pylock.py
ADDED
|
@@ -0,0 +1,905 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import dataclasses
|
| 4 |
+
import logging
|
| 5 |
+
import re
|
| 6 |
+
from collections.abc import Mapping, Sequence
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
+
from datetime import datetime
|
| 9 |
+
from typing import (
|
| 10 |
+
TYPE_CHECKING,
|
| 11 |
+
Any,
|
| 12 |
+
Callable,
|
| 13 |
+
Protocol,
|
| 14 |
+
TypeVar,
|
| 15 |
+
cast,
|
| 16 |
+
)
|
| 17 |
+
from urllib.parse import urlparse
|
| 18 |
+
|
| 19 |
+
from .markers import Environment, Marker, default_environment
|
| 20 |
+
from .specifiers import SpecifierSet
|
| 21 |
+
from .tags import create_compatible_tags_selector, sys_tags
|
| 22 |
+
from .utils import (
|
| 23 |
+
NormalizedName,
|
| 24 |
+
is_normalized_name,
|
| 25 |
+
parse_sdist_filename,
|
| 26 |
+
parse_wheel_filename,
|
| 27 |
+
)
|
| 28 |
+
from .version import Version
|
| 29 |
+
|
| 30 |
+
if TYPE_CHECKING: # pragma: no cover
|
| 31 |
+
from collections.abc import Collection, Iterator
|
| 32 |
+
from pathlib import Path
|
| 33 |
+
|
| 34 |
+
from typing_extensions import Self
|
| 35 |
+
|
| 36 |
+
from .tags import Tag
|
| 37 |
+
|
| 38 |
+
_logger = logging.getLogger(__name__)
|
| 39 |
+
|
| 40 |
+
__all__ = [
|
| 41 |
+
"Package",
|
| 42 |
+
"PackageArchive",
|
| 43 |
+
"PackageDirectory",
|
| 44 |
+
"PackageSdist",
|
| 45 |
+
"PackageVcs",
|
| 46 |
+
"PackageWheel",
|
| 47 |
+
"Pylock",
|
| 48 |
+
"PylockUnsupportedVersionError",
|
| 49 |
+
"PylockValidationError",
|
| 50 |
+
"is_valid_pylock_path",
|
| 51 |
+
]
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def __dir__() -> list[str]:
|
| 55 |
+
return __all__
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
_T = TypeVar("_T")
|
| 59 |
+
_T2 = TypeVar("_T2")
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
class _FromMappingProtocol(Protocol): # pragma: no cover
|
| 63 |
+
@classmethod
|
| 64 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self: ...
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
_FromMappingProtocolT = TypeVar("_FromMappingProtocolT", bound=_FromMappingProtocol)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
_PYLOCK_FILE_NAME_RE = re.compile(r"^pylock\.([^.]+)\.toml$")
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def is_valid_pylock_path(path: Path) -> bool:
|
| 74 |
+
"""Check if the given path is a valid pylock file path."""
|
| 75 |
+
return path.name == "pylock.toml" or bool(_PYLOCK_FILE_NAME_RE.match(path.name))
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def _toml_key(key: str) -> str:
|
| 79 |
+
return key.replace("_", "-")
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def _toml_value(key: str, value: Any) -> Any: # noqa: ANN401
|
| 83 |
+
if isinstance(value, (Version, Marker, SpecifierSet)):
|
| 84 |
+
return str(value)
|
| 85 |
+
if isinstance(value, Sequence) and key == "environments":
|
| 86 |
+
return [str(v) for v in value]
|
| 87 |
+
return value
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def _toml_dict_factory(data: list[tuple[str, Any]]) -> dict[str, Any]:
|
| 91 |
+
return {
|
| 92 |
+
_toml_key(key): _toml_value(key, value)
|
| 93 |
+
for key, value in data
|
| 94 |
+
if value is not None
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def _get(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T | None:
|
| 99 |
+
"""Get a value from the dictionary and verify it's the expected type."""
|
| 100 |
+
if (value := d.get(key)) is None:
|
| 101 |
+
return None
|
| 102 |
+
if not isinstance(value, expected_type):
|
| 103 |
+
raise PylockValidationError(
|
| 104 |
+
f"Unexpected type {type(value).__name__} "
|
| 105 |
+
f"(expected {expected_type.__name__})",
|
| 106 |
+
context=key,
|
| 107 |
+
)
|
| 108 |
+
return value
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def _get_required(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T:
|
| 112 |
+
"""Get a required value from the dictionary and verify it's the expected type."""
|
| 113 |
+
if (value := _get(d, expected_type, key)) is None:
|
| 114 |
+
raise _PylockRequiredKeyError(key)
|
| 115 |
+
return value
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def _get_sequence(
|
| 119 |
+
d: Mapping[str, Any], expected_item_type: type[_T], key: str
|
| 120 |
+
) -> Sequence[_T] | None:
|
| 121 |
+
"""Get a list value from the dictionary and verify it's the expected items type."""
|
| 122 |
+
if (value := _get(d, Sequence, key)) is None: # type: ignore[type-abstract]
|
| 123 |
+
return None
|
| 124 |
+
if isinstance(value, (str, bytes)):
|
| 125 |
+
# special case: str and bytes are Sequences, but we want to reject it
|
| 126 |
+
raise PylockValidationError(
|
| 127 |
+
f"Unexpected type {type(value).__name__} (expected Sequence)",
|
| 128 |
+
context=key,
|
| 129 |
+
)
|
| 130 |
+
for i, item in enumerate(value):
|
| 131 |
+
if not isinstance(item, expected_item_type):
|
| 132 |
+
raise PylockValidationError(
|
| 133 |
+
f"Unexpected type {type(item).__name__} "
|
| 134 |
+
f"(expected {expected_item_type.__name__})",
|
| 135 |
+
context=f"{key}[{i}]",
|
| 136 |
+
)
|
| 137 |
+
return value
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def _get_as(
|
| 141 |
+
d: Mapping[str, Any],
|
| 142 |
+
expected_type: type[_T],
|
| 143 |
+
target_type: Callable[[_T], _T2],
|
| 144 |
+
key: str,
|
| 145 |
+
) -> _T2 | None:
|
| 146 |
+
"""Get a value from the dictionary, verify it's the expected type,
|
| 147 |
+
and convert to the target type.
|
| 148 |
+
|
| 149 |
+
This assumes the target_type constructor accepts the value.
|
| 150 |
+
"""
|
| 151 |
+
if (value := _get(d, expected_type, key)) is None:
|
| 152 |
+
return None
|
| 153 |
+
try:
|
| 154 |
+
return target_type(value)
|
| 155 |
+
except Exception as e:
|
| 156 |
+
raise PylockValidationError(e, context=key) from e
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def _get_required_as(
|
| 160 |
+
d: Mapping[str, Any],
|
| 161 |
+
expected_type: type[_T],
|
| 162 |
+
target_type: Callable[[_T], _T2],
|
| 163 |
+
key: str,
|
| 164 |
+
) -> _T2:
|
| 165 |
+
"""Get a required value from the dict, verify it's the expected type,
|
| 166 |
+
and convert to the target type."""
|
| 167 |
+
if (value := _get_as(d, expected_type, target_type, key)) is None:
|
| 168 |
+
raise _PylockRequiredKeyError(key)
|
| 169 |
+
return value
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def _get_sequence_as(
|
| 173 |
+
d: Mapping[str, Any],
|
| 174 |
+
expected_item_type: type[_T],
|
| 175 |
+
target_item_type: Callable[[_T], _T2],
|
| 176 |
+
key: str,
|
| 177 |
+
) -> list[_T2] | None:
|
| 178 |
+
"""Get list value from dictionary and verify expected items type."""
|
| 179 |
+
if (value := _get_sequence(d, expected_item_type, key)) is None:
|
| 180 |
+
return None
|
| 181 |
+
result = []
|
| 182 |
+
try:
|
| 183 |
+
for item in value:
|
| 184 |
+
typed_item = target_item_type(item)
|
| 185 |
+
result.append(typed_item)
|
| 186 |
+
except Exception as e:
|
| 187 |
+
raise PylockValidationError(e, context=f"{key}[{len(result)}]") from e
|
| 188 |
+
return result
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def _get_object(
|
| 192 |
+
d: Mapping[str, Any], target_type: type[_FromMappingProtocolT], key: str
|
| 193 |
+
) -> _FromMappingProtocolT | None:
|
| 194 |
+
"""Get a dictionary value from the dictionary and convert it to a dataclass."""
|
| 195 |
+
if (value := _get(d, Mapping, key)) is None: # type: ignore[type-abstract]
|
| 196 |
+
return None
|
| 197 |
+
try:
|
| 198 |
+
return target_type._from_dict(value)
|
| 199 |
+
except Exception as e:
|
| 200 |
+
raise PylockValidationError(e, context=key) from e
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def _get_sequence_of_objects(
|
| 204 |
+
d: Mapping[str, Any], target_item_type: type[_FromMappingProtocolT], key: str
|
| 205 |
+
) -> list[_FromMappingProtocolT] | None:
|
| 206 |
+
"""Get a list value from the dictionary and convert its items to a dataclass."""
|
| 207 |
+
if (value := _get_sequence(d, Mapping, key)) is None: # type: ignore[type-abstract]
|
| 208 |
+
return None
|
| 209 |
+
result: list[_FromMappingProtocolT] = []
|
| 210 |
+
try:
|
| 211 |
+
for item in value:
|
| 212 |
+
typed_item = target_item_type._from_dict(item)
|
| 213 |
+
result.append(typed_item)
|
| 214 |
+
except Exception as e:
|
| 215 |
+
raise PylockValidationError(e, context=f"{key}[{len(result)}]") from e
|
| 216 |
+
return result
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def _get_required_sequence_of_objects(
|
| 220 |
+
d: Mapping[str, Any], target_item_type: type[_FromMappingProtocolT], key: str
|
| 221 |
+
) -> Sequence[_FromMappingProtocolT]:
|
| 222 |
+
"""Get a required list value from the dictionary and convert its items to a
|
| 223 |
+
dataclass."""
|
| 224 |
+
if (result := _get_sequence_of_objects(d, target_item_type, key)) is None:
|
| 225 |
+
raise _PylockRequiredKeyError(key)
|
| 226 |
+
return result
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
def _validate_normalized_name(name: str) -> NormalizedName:
|
| 230 |
+
"""Validate that a string is a NormalizedName."""
|
| 231 |
+
if not is_normalized_name(name):
|
| 232 |
+
raise PylockValidationError(f"Name {name!r} is not normalized")
|
| 233 |
+
return NormalizedName(name)
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
def _validate_path_url(path: str | None, url: str | None) -> None:
|
| 237 |
+
if not path and not url:
|
| 238 |
+
raise PylockValidationError("path or url must be provided")
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
def _path_name(path: str | None) -> str | None:
|
| 242 |
+
if not path:
|
| 243 |
+
return None
|
| 244 |
+
# If the path is relative it MAY use POSIX-style path separators explicitly
|
| 245 |
+
# for portability
|
| 246 |
+
if "/" in path:
|
| 247 |
+
return path.rsplit("/", 1)[-1]
|
| 248 |
+
elif "\\" in path:
|
| 249 |
+
return path.rsplit("\\", 1)[-1]
|
| 250 |
+
else:
|
| 251 |
+
return path
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
def _url_name(url: str | None) -> str | None:
|
| 255 |
+
if not url:
|
| 256 |
+
return None
|
| 257 |
+
url_path = urlparse(url).path
|
| 258 |
+
return url_path.rsplit("/", 1)[-1]
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
def _validate_hashes(hashes: Mapping[str, Any]) -> Mapping[str, Any]:
|
| 262 |
+
if not hashes:
|
| 263 |
+
raise PylockValidationError("At least one hash must be provided")
|
| 264 |
+
if not all(isinstance(hash_val, str) for hash_val in hashes.values()):
|
| 265 |
+
raise PylockValidationError("Hash values must be strings")
|
| 266 |
+
return hashes
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
class PylockValidationError(Exception):
|
| 270 |
+
"""Raised when when input data is not spec-compliant."""
|
| 271 |
+
|
| 272 |
+
context: str | None = None
|
| 273 |
+
message: str
|
| 274 |
+
|
| 275 |
+
def __init__(
|
| 276 |
+
self,
|
| 277 |
+
cause: str | Exception,
|
| 278 |
+
*,
|
| 279 |
+
context: str | None = None,
|
| 280 |
+
) -> None:
|
| 281 |
+
if isinstance(cause, PylockValidationError):
|
| 282 |
+
if cause.context:
|
| 283 |
+
self.context = (
|
| 284 |
+
f"{context}.{cause.context}" if context else cause.context
|
| 285 |
+
)
|
| 286 |
+
else:
|
| 287 |
+
self.context = context
|
| 288 |
+
self.message = cause.message
|
| 289 |
+
else:
|
| 290 |
+
self.context = context
|
| 291 |
+
self.message = str(cause)
|
| 292 |
+
|
| 293 |
+
def __str__(self) -> str:
|
| 294 |
+
if self.context:
|
| 295 |
+
return f"{self.message} in {self.context!r}"
|
| 296 |
+
return self.message
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
class _PylockRequiredKeyError(PylockValidationError):
|
| 300 |
+
def __init__(self, key: str) -> None:
|
| 301 |
+
super().__init__("Missing required value", context=key)
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
class PylockUnsupportedVersionError(PylockValidationError):
|
| 305 |
+
"""Raised when encountering an unsupported `lock_version`."""
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
class PylockSelectError(Exception):
|
| 309 |
+
"""Base exception for errors raised by :meth:`Pylock.select`."""
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
@dataclass(frozen=True, init=False)
|
| 313 |
+
class PackageVcs:
|
| 314 |
+
type: str
|
| 315 |
+
url: str | None = None
|
| 316 |
+
path: str | None = None
|
| 317 |
+
requested_revision: str | None = None
|
| 318 |
+
commit_id: str # type: ignore[misc]
|
| 319 |
+
subdirectory: str | None = None
|
| 320 |
+
|
| 321 |
+
def __init__(
|
| 322 |
+
self,
|
| 323 |
+
*,
|
| 324 |
+
type: str,
|
| 325 |
+
url: str | None = None,
|
| 326 |
+
path: str | None = None,
|
| 327 |
+
requested_revision: str | None = None,
|
| 328 |
+
commit_id: str,
|
| 329 |
+
subdirectory: str | None = None,
|
| 330 |
+
) -> None:
|
| 331 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 332 |
+
object.__setattr__(self, "type", type)
|
| 333 |
+
object.__setattr__(self, "url", url)
|
| 334 |
+
object.__setattr__(self, "path", path)
|
| 335 |
+
object.__setattr__(self, "requested_revision", requested_revision)
|
| 336 |
+
object.__setattr__(self, "commit_id", commit_id)
|
| 337 |
+
object.__setattr__(self, "subdirectory", subdirectory)
|
| 338 |
+
|
| 339 |
+
@classmethod
|
| 340 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 341 |
+
package_vcs = cls(
|
| 342 |
+
type=_get_required(d, str, "type"),
|
| 343 |
+
url=_get(d, str, "url"),
|
| 344 |
+
path=_get(d, str, "path"),
|
| 345 |
+
requested_revision=_get(d, str, "requested-revision"),
|
| 346 |
+
commit_id=_get_required(d, str, "commit-id"),
|
| 347 |
+
subdirectory=_get(d, str, "subdirectory"),
|
| 348 |
+
)
|
| 349 |
+
_validate_path_url(package_vcs.path, package_vcs.url)
|
| 350 |
+
return package_vcs
|
| 351 |
+
|
| 352 |
+
|
| 353 |
+
@dataclass(frozen=True, init=False)
|
| 354 |
+
class PackageDirectory:
|
| 355 |
+
path: str
|
| 356 |
+
editable: bool | None = None
|
| 357 |
+
subdirectory: str | None = None
|
| 358 |
+
|
| 359 |
+
def __init__(
|
| 360 |
+
self,
|
| 361 |
+
*,
|
| 362 |
+
path: str,
|
| 363 |
+
editable: bool | None = None,
|
| 364 |
+
subdirectory: str | None = None,
|
| 365 |
+
) -> None:
|
| 366 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 367 |
+
object.__setattr__(self, "path", path)
|
| 368 |
+
object.__setattr__(self, "editable", editable)
|
| 369 |
+
object.__setattr__(self, "subdirectory", subdirectory)
|
| 370 |
+
|
| 371 |
+
@classmethod
|
| 372 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 373 |
+
return cls(
|
| 374 |
+
path=_get_required(d, str, "path"),
|
| 375 |
+
editable=_get(d, bool, "editable"),
|
| 376 |
+
subdirectory=_get(d, str, "subdirectory"),
|
| 377 |
+
)
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
@dataclass(frozen=True, init=False)
|
| 381 |
+
class PackageArchive:
|
| 382 |
+
url: str | None = None
|
| 383 |
+
path: str | None = None
|
| 384 |
+
size: int | None = None
|
| 385 |
+
upload_time: datetime | None = None
|
| 386 |
+
hashes: Mapping[str, str] # type: ignore[misc]
|
| 387 |
+
subdirectory: str | None = None
|
| 388 |
+
|
| 389 |
+
def __init__(
|
| 390 |
+
self,
|
| 391 |
+
*,
|
| 392 |
+
url: str | None = None,
|
| 393 |
+
path: str | None = None,
|
| 394 |
+
size: int | None = None,
|
| 395 |
+
upload_time: datetime | None = None,
|
| 396 |
+
hashes: Mapping[str, str],
|
| 397 |
+
subdirectory: str | None = None,
|
| 398 |
+
) -> None:
|
| 399 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 400 |
+
object.__setattr__(self, "url", url)
|
| 401 |
+
object.__setattr__(self, "path", path)
|
| 402 |
+
object.__setattr__(self, "size", size)
|
| 403 |
+
object.__setattr__(self, "upload_time", upload_time)
|
| 404 |
+
object.__setattr__(self, "hashes", hashes)
|
| 405 |
+
object.__setattr__(self, "subdirectory", subdirectory)
|
| 406 |
+
|
| 407 |
+
@classmethod
|
| 408 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 409 |
+
package_archive = cls(
|
| 410 |
+
url=_get(d, str, "url"),
|
| 411 |
+
path=_get(d, str, "path"),
|
| 412 |
+
size=_get(d, int, "size"),
|
| 413 |
+
upload_time=_get(d, datetime, "upload-time"),
|
| 414 |
+
hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"), # type: ignore[type-abstract]
|
| 415 |
+
subdirectory=_get(d, str, "subdirectory"),
|
| 416 |
+
)
|
| 417 |
+
_validate_path_url(package_archive.path, package_archive.url)
|
| 418 |
+
return package_archive
|
| 419 |
+
|
| 420 |
+
|
| 421 |
+
@dataclass(frozen=True, init=False)
|
| 422 |
+
class PackageSdist:
|
| 423 |
+
name: str | None = None
|
| 424 |
+
upload_time: datetime | None = None
|
| 425 |
+
url: str | None = None
|
| 426 |
+
path: str | None = None
|
| 427 |
+
size: int | None = None
|
| 428 |
+
hashes: Mapping[str, str] # type: ignore[misc]
|
| 429 |
+
|
| 430 |
+
def __init__(
|
| 431 |
+
self,
|
| 432 |
+
*,
|
| 433 |
+
name: str | None = None,
|
| 434 |
+
upload_time: datetime | None = None,
|
| 435 |
+
url: str | None = None,
|
| 436 |
+
path: str | None = None,
|
| 437 |
+
size: int | None = None,
|
| 438 |
+
hashes: Mapping[str, str],
|
| 439 |
+
) -> None:
|
| 440 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 441 |
+
object.__setattr__(self, "name", name)
|
| 442 |
+
object.__setattr__(self, "upload_time", upload_time)
|
| 443 |
+
object.__setattr__(self, "url", url)
|
| 444 |
+
object.__setattr__(self, "path", path)
|
| 445 |
+
object.__setattr__(self, "size", size)
|
| 446 |
+
object.__setattr__(self, "hashes", hashes)
|
| 447 |
+
|
| 448 |
+
@classmethod
|
| 449 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 450 |
+
package_sdist = cls(
|
| 451 |
+
name=_get(d, str, "name"),
|
| 452 |
+
upload_time=_get(d, datetime, "upload-time"),
|
| 453 |
+
url=_get(d, str, "url"),
|
| 454 |
+
path=_get(d, str, "path"),
|
| 455 |
+
size=_get(d, int, "size"),
|
| 456 |
+
hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"), # type: ignore[type-abstract]
|
| 457 |
+
)
|
| 458 |
+
_validate_path_url(package_sdist.path, package_sdist.url)
|
| 459 |
+
return package_sdist
|
| 460 |
+
|
| 461 |
+
@property
|
| 462 |
+
def filename(self) -> str:
|
| 463 |
+
"""Get the filename of the sdist."""
|
| 464 |
+
filename = self.name or _path_name(self.path) or _url_name(self.url)
|
| 465 |
+
if not filename:
|
| 466 |
+
raise PylockValidationError("Cannot determine sdist filename")
|
| 467 |
+
return filename
|
| 468 |
+
|
| 469 |
+
|
| 470 |
+
@dataclass(frozen=True, init=False)
|
| 471 |
+
class PackageWheel:
|
| 472 |
+
name: str | None = None
|
| 473 |
+
upload_time: datetime | None = None
|
| 474 |
+
url: str | None = None
|
| 475 |
+
path: str | None = None
|
| 476 |
+
size: int | None = None
|
| 477 |
+
hashes: Mapping[str, str] # type: ignore[misc]
|
| 478 |
+
|
| 479 |
+
def __init__(
|
| 480 |
+
self,
|
| 481 |
+
*,
|
| 482 |
+
name: str | None = None,
|
| 483 |
+
upload_time: datetime | None = None,
|
| 484 |
+
url: str | None = None,
|
| 485 |
+
path: str | None = None,
|
| 486 |
+
size: int | None = None,
|
| 487 |
+
hashes: Mapping[str, str],
|
| 488 |
+
) -> None:
|
| 489 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 490 |
+
object.__setattr__(self, "name", name)
|
| 491 |
+
object.__setattr__(self, "upload_time", upload_time)
|
| 492 |
+
object.__setattr__(self, "url", url)
|
| 493 |
+
object.__setattr__(self, "path", path)
|
| 494 |
+
object.__setattr__(self, "size", size)
|
| 495 |
+
object.__setattr__(self, "hashes", hashes)
|
| 496 |
+
|
| 497 |
+
@classmethod
|
| 498 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 499 |
+
package_wheel = cls(
|
| 500 |
+
name=_get(d, str, "name"),
|
| 501 |
+
upload_time=_get(d, datetime, "upload-time"),
|
| 502 |
+
url=_get(d, str, "url"),
|
| 503 |
+
path=_get(d, str, "path"),
|
| 504 |
+
size=_get(d, int, "size"),
|
| 505 |
+
hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"), # type: ignore[type-abstract]
|
| 506 |
+
)
|
| 507 |
+
_validate_path_url(package_wheel.path, package_wheel.url)
|
| 508 |
+
return package_wheel
|
| 509 |
+
|
| 510 |
+
@property
|
| 511 |
+
def filename(self) -> str:
|
| 512 |
+
"""Get the filename of the wheel."""
|
| 513 |
+
filename = self.name or _path_name(self.path) or _url_name(self.url)
|
| 514 |
+
if not filename:
|
| 515 |
+
raise PylockValidationError("Cannot determine wheel filename")
|
| 516 |
+
return filename
|
| 517 |
+
|
| 518 |
+
|
| 519 |
+
@dataclass(frozen=True, init=False)
|
| 520 |
+
class Package:
|
| 521 |
+
name: NormalizedName
|
| 522 |
+
version: Version | None = None
|
| 523 |
+
marker: Marker | None = None
|
| 524 |
+
requires_python: SpecifierSet | None = None
|
| 525 |
+
dependencies: Sequence[Mapping[str, Any]] | None = None
|
| 526 |
+
vcs: PackageVcs | None = None
|
| 527 |
+
directory: PackageDirectory | None = None
|
| 528 |
+
archive: PackageArchive | None = None
|
| 529 |
+
index: str | None = None
|
| 530 |
+
sdist: PackageSdist | None = None
|
| 531 |
+
wheels: Sequence[PackageWheel] | None = None
|
| 532 |
+
attestation_identities: Sequence[Mapping[str, Any]] | None = None
|
| 533 |
+
tool: Mapping[str, Any] | None = None
|
| 534 |
+
|
| 535 |
+
def __init__(
|
| 536 |
+
self,
|
| 537 |
+
*,
|
| 538 |
+
name: NormalizedName,
|
| 539 |
+
version: Version | None = None,
|
| 540 |
+
marker: Marker | None = None,
|
| 541 |
+
requires_python: SpecifierSet | None = None,
|
| 542 |
+
dependencies: Sequence[Mapping[str, Any]] | None = None,
|
| 543 |
+
vcs: PackageVcs | None = None,
|
| 544 |
+
directory: PackageDirectory | None = None,
|
| 545 |
+
archive: PackageArchive | None = None,
|
| 546 |
+
index: str | None = None,
|
| 547 |
+
sdist: PackageSdist | None = None,
|
| 548 |
+
wheels: Sequence[PackageWheel] | None = None,
|
| 549 |
+
attestation_identities: Sequence[Mapping[str, Any]] | None = None,
|
| 550 |
+
tool: Mapping[str, Any] | None = None,
|
| 551 |
+
) -> None:
|
| 552 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 553 |
+
object.__setattr__(self, "name", name)
|
| 554 |
+
object.__setattr__(self, "version", version)
|
| 555 |
+
object.__setattr__(self, "marker", marker)
|
| 556 |
+
object.__setattr__(self, "requires_python", requires_python)
|
| 557 |
+
object.__setattr__(self, "dependencies", dependencies)
|
| 558 |
+
object.__setattr__(self, "vcs", vcs)
|
| 559 |
+
object.__setattr__(self, "directory", directory)
|
| 560 |
+
object.__setattr__(self, "archive", archive)
|
| 561 |
+
object.__setattr__(self, "index", index)
|
| 562 |
+
object.__setattr__(self, "sdist", sdist)
|
| 563 |
+
object.__setattr__(self, "wheels", wheels)
|
| 564 |
+
object.__setattr__(self, "attestation_identities", attestation_identities)
|
| 565 |
+
object.__setattr__(self, "tool", tool)
|
| 566 |
+
|
| 567 |
+
@classmethod
|
| 568 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 569 |
+
package = cls(
|
| 570 |
+
name=_get_required_as(d, str, _validate_normalized_name, "name"),
|
| 571 |
+
version=_get_as(d, str, Version, "version"),
|
| 572 |
+
requires_python=_get_as(d, str, SpecifierSet, "requires-python"),
|
| 573 |
+
dependencies=_get_sequence(d, Mapping, "dependencies"), # type: ignore[type-abstract]
|
| 574 |
+
marker=_get_as(d, str, Marker, "marker"),
|
| 575 |
+
vcs=_get_object(d, PackageVcs, "vcs"),
|
| 576 |
+
directory=_get_object(d, PackageDirectory, "directory"),
|
| 577 |
+
archive=_get_object(d, PackageArchive, "archive"),
|
| 578 |
+
index=_get(d, str, "index"),
|
| 579 |
+
sdist=_get_object(d, PackageSdist, "sdist"),
|
| 580 |
+
wheels=_get_sequence_of_objects(d, PackageWheel, "wheels"),
|
| 581 |
+
attestation_identities=_get_sequence(d, Mapping, "attestation-identities"), # type: ignore[type-abstract]
|
| 582 |
+
tool=_get(d, Mapping, "tool"), # type: ignore[type-abstract]
|
| 583 |
+
)
|
| 584 |
+
distributions = bool(package.sdist) + len(package.wheels or [])
|
| 585 |
+
direct_urls = (
|
| 586 |
+
bool(package.vcs) + bool(package.directory) + bool(package.archive)
|
| 587 |
+
)
|
| 588 |
+
if distributions > 0 and direct_urls > 0:
|
| 589 |
+
raise PylockValidationError(
|
| 590 |
+
"None of vcs, directory, archive must be set if sdist or wheels are set"
|
| 591 |
+
)
|
| 592 |
+
if distributions == 0 and direct_urls != 1:
|
| 593 |
+
raise PylockValidationError(
|
| 594 |
+
"Exactly one of vcs, directory, archive must be set "
|
| 595 |
+
"if sdist and wheels are not set"
|
| 596 |
+
)
|
| 597 |
+
for i, wheel in enumerate(package.wheels or []):
|
| 598 |
+
try:
|
| 599 |
+
(name, version, _, _) = parse_wheel_filename(wheel.filename)
|
| 600 |
+
except Exception as e:
|
| 601 |
+
raise PylockValidationError(
|
| 602 |
+
f"Invalid wheel filename {wheel.filename!r}",
|
| 603 |
+
context=f"wheels[{i}]",
|
| 604 |
+
) from e
|
| 605 |
+
if name != package.name:
|
| 606 |
+
raise PylockValidationError(
|
| 607 |
+
f"Name in {wheel.filename!r} is not consistent with "
|
| 608 |
+
f"package name {package.name!r}",
|
| 609 |
+
context=f"wheels[{i}]",
|
| 610 |
+
)
|
| 611 |
+
if package.version and version != package.version:
|
| 612 |
+
raise PylockValidationError(
|
| 613 |
+
f"Version in {wheel.filename!r} is not consistent with "
|
| 614 |
+
f"package version {str(package.version)!r}",
|
| 615 |
+
context=f"wheels[{i}]",
|
| 616 |
+
)
|
| 617 |
+
if package.sdist:
|
| 618 |
+
try:
|
| 619 |
+
name, version = parse_sdist_filename(package.sdist.filename)
|
| 620 |
+
except Exception as e:
|
| 621 |
+
raise PylockValidationError(
|
| 622 |
+
f"Invalid sdist filename {package.sdist.filename!r}",
|
| 623 |
+
context="sdist",
|
| 624 |
+
) from e
|
| 625 |
+
if name != package.name:
|
| 626 |
+
raise PylockValidationError(
|
| 627 |
+
f"Name in {package.sdist.filename!r} is not consistent with "
|
| 628 |
+
f"package name {package.name!r}",
|
| 629 |
+
context="sdist",
|
| 630 |
+
)
|
| 631 |
+
if package.version and version != package.version:
|
| 632 |
+
raise PylockValidationError(
|
| 633 |
+
f"Version in {package.sdist.filename!r} is not consistent with "
|
| 634 |
+
f"package version {str(package.version)!r}",
|
| 635 |
+
context="sdist",
|
| 636 |
+
)
|
| 637 |
+
try:
|
| 638 |
+
for i, attestation_identity in enumerate( # noqa: B007
|
| 639 |
+
package.attestation_identities or []
|
| 640 |
+
):
|
| 641 |
+
_get_required(attestation_identity, str, "kind")
|
| 642 |
+
except Exception as e:
|
| 643 |
+
raise PylockValidationError(
|
| 644 |
+
e, context=f"attestation-identities[{i}]"
|
| 645 |
+
) from e
|
| 646 |
+
return package
|
| 647 |
+
|
| 648 |
+
@property
|
| 649 |
+
def is_direct(self) -> bool:
|
| 650 |
+
return not (self.sdist or self.wheels)
|
| 651 |
+
|
| 652 |
+
|
| 653 |
+
@dataclass(frozen=True, init=False)
|
| 654 |
+
class Pylock:
|
| 655 |
+
"""A class representing a pylock file."""
|
| 656 |
+
|
| 657 |
+
lock_version: Version
|
| 658 |
+
environments: Sequence[Marker] | None = None
|
| 659 |
+
requires_python: SpecifierSet | None = None
|
| 660 |
+
extras: Sequence[NormalizedName] | None = None
|
| 661 |
+
dependency_groups: Sequence[str] | None = None
|
| 662 |
+
default_groups: Sequence[str] | None = None
|
| 663 |
+
created_by: str # type: ignore[misc]
|
| 664 |
+
packages: Sequence[Package] # type: ignore[misc]
|
| 665 |
+
tool: Mapping[str, Any] | None = None
|
| 666 |
+
|
| 667 |
+
def __init__(
|
| 668 |
+
self,
|
| 669 |
+
*,
|
| 670 |
+
lock_version: Version,
|
| 671 |
+
environments: Sequence[Marker] | None = None,
|
| 672 |
+
requires_python: SpecifierSet | None = None,
|
| 673 |
+
extras: Sequence[NormalizedName] | None = None,
|
| 674 |
+
dependency_groups: Sequence[str] | None = None,
|
| 675 |
+
default_groups: Sequence[str] | None = None,
|
| 676 |
+
created_by: str,
|
| 677 |
+
packages: Sequence[Package],
|
| 678 |
+
tool: Mapping[str, Any] | None = None,
|
| 679 |
+
) -> None:
|
| 680 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 681 |
+
object.__setattr__(self, "lock_version", lock_version)
|
| 682 |
+
object.__setattr__(self, "environments", environments)
|
| 683 |
+
object.__setattr__(self, "requires_python", requires_python)
|
| 684 |
+
object.__setattr__(self, "extras", extras)
|
| 685 |
+
object.__setattr__(self, "dependency_groups", dependency_groups)
|
| 686 |
+
object.__setattr__(self, "default_groups", default_groups)
|
| 687 |
+
object.__setattr__(self, "created_by", created_by)
|
| 688 |
+
object.__setattr__(self, "packages", packages)
|
| 689 |
+
object.__setattr__(self, "tool", tool)
|
| 690 |
+
|
| 691 |
+
@classmethod
|
| 692 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 693 |
+
pylock = cls(
|
| 694 |
+
lock_version=_get_required_as(d, str, Version, "lock-version"),
|
| 695 |
+
environments=_get_sequence_as(d, str, Marker, "environments"),
|
| 696 |
+
extras=_get_sequence_as(d, str, _validate_normalized_name, "extras"),
|
| 697 |
+
dependency_groups=_get_sequence(d, str, "dependency-groups"),
|
| 698 |
+
default_groups=_get_sequence(d, str, "default-groups"),
|
| 699 |
+
created_by=_get_required(d, str, "created-by"),
|
| 700 |
+
requires_python=_get_as(d, str, SpecifierSet, "requires-python"),
|
| 701 |
+
packages=_get_required_sequence_of_objects(d, Package, "packages"),
|
| 702 |
+
tool=_get(d, Mapping, "tool"), # type: ignore[type-abstract]
|
| 703 |
+
)
|
| 704 |
+
if not Version("1") <= pylock.lock_version < Version("2"):
|
| 705 |
+
raise PylockUnsupportedVersionError(
|
| 706 |
+
f"pylock version {pylock.lock_version} is not supported"
|
| 707 |
+
)
|
| 708 |
+
if pylock.lock_version > Version("1.0"):
|
| 709 |
+
_logger.warning(
|
| 710 |
+
"pylock minor version %s is not supported", pylock.lock_version
|
| 711 |
+
)
|
| 712 |
+
return pylock
|
| 713 |
+
|
| 714 |
+
@classmethod
|
| 715 |
+
def from_dict(cls, d: Mapping[str, Any], /) -> Self:
|
| 716 |
+
"""Create and validate a Pylock instance from a TOML dictionary.
|
| 717 |
+
|
| 718 |
+
Raises :class:`PylockValidationError` if the input data is not
|
| 719 |
+
spec-compliant.
|
| 720 |
+
"""
|
| 721 |
+
return cls._from_dict(d)
|
| 722 |
+
|
| 723 |
+
def to_dict(self) -> Mapping[str, Any]:
|
| 724 |
+
"""Convert the Pylock instance to a TOML dictionary."""
|
| 725 |
+
return dataclasses.asdict(self, dict_factory=_toml_dict_factory)
|
| 726 |
+
|
| 727 |
+
def validate(self) -> None:
|
| 728 |
+
"""Validate the Pylock instance against the specification.
|
| 729 |
+
|
| 730 |
+
Raises :class:`PylockValidationError` otherwise."""
|
| 731 |
+
self.from_dict(self.to_dict())
|
| 732 |
+
|
| 733 |
+
def select(
|
| 734 |
+
self,
|
| 735 |
+
*,
|
| 736 |
+
environment: Environment | None = None,
|
| 737 |
+
tags: Sequence[Tag] | None = None,
|
| 738 |
+
extras: Collection[str] | None = None,
|
| 739 |
+
dependency_groups: Collection[str] | None = None,
|
| 740 |
+
) -> Iterator[
|
| 741 |
+
tuple[
|
| 742 |
+
Package,
|
| 743 |
+
PackageVcs
|
| 744 |
+
| PackageDirectory
|
| 745 |
+
| PackageArchive
|
| 746 |
+
| PackageWheel
|
| 747 |
+
| PackageSdist,
|
| 748 |
+
]
|
| 749 |
+
]:
|
| 750 |
+
"""Select what to install from the lock file.
|
| 751 |
+
|
| 752 |
+
The *environment* and *tags* parameters represent the environment being
|
| 753 |
+
selected for. If unspecified, ``packaging.markers.default_environment()`` and
|
| 754 |
+
``packaging.tags.sys_tags()`` are used.
|
| 755 |
+
|
| 756 |
+
The *extras* parameter represents the extras to install.
|
| 757 |
+
|
| 758 |
+
The *dependency_groups* parameter represents the groups to install. If
|
| 759 |
+
unspecified, the default groups are used.
|
| 760 |
+
|
| 761 |
+
This method must be used on valid Pylock instances (i.e. one obtained
|
| 762 |
+
from :meth:`Pylock.from_dict` or if constructed manually, after calling
|
| 763 |
+
:meth:`Pylock.validate`).
|
| 764 |
+
"""
|
| 765 |
+
compatible_tags_selector = create_compatible_tags_selector(tags or sys_tags())
|
| 766 |
+
|
| 767 |
+
# #. Gather the extras and dependency groups to install and set ``extras`` and
|
| 768 |
+
# ``dependency_groups`` for marker evaluation, respectively.
|
| 769 |
+
#
|
| 770 |
+
# #. ``extras`` SHOULD be set to the empty set by default.
|
| 771 |
+
# #. ``dependency_groups`` SHOULD be the set created from
|
| 772 |
+
# :ref:`pylock-default-groups` by default.
|
| 773 |
+
env = cast(
|
| 774 |
+
"dict[str, str | frozenset[str]]",
|
| 775 |
+
dict(
|
| 776 |
+
environment or {}, # Marker.evaluate will fill-up
|
| 777 |
+
extras=frozenset(extras or []),
|
| 778 |
+
dependency_groups=frozenset(
|
| 779 |
+
(self.default_groups or [])
|
| 780 |
+
if dependency_groups is None # to allow selecting no group
|
| 781 |
+
else dependency_groups
|
| 782 |
+
),
|
| 783 |
+
),
|
| 784 |
+
)
|
| 785 |
+
env_python_full_version = (
|
| 786 |
+
environment["python_full_version"]
|
| 787 |
+
if environment
|
| 788 |
+
else default_environment()["python_full_version"]
|
| 789 |
+
)
|
| 790 |
+
|
| 791 |
+
# #. Check if the metadata version specified by :ref:`pylock-lock-version` is
|
| 792 |
+
# supported; an error or warning MUST be raised as appropriate.
|
| 793 |
+
# Covered by lock.validate() which is a precondition for this method.
|
| 794 |
+
|
| 795 |
+
# #. If :ref:`pylock-requires-python` is specified, check that the environment
|
| 796 |
+
# being installed for meets the requirement; an error MUST be raised if it is
|
| 797 |
+
# not met.
|
| 798 |
+
if self.requires_python and not self.requires_python.contains(
|
| 799 |
+
env_python_full_version,
|
| 800 |
+
):
|
| 801 |
+
raise PylockSelectError(
|
| 802 |
+
f"python_full_version {env_python_full_version!r} "
|
| 803 |
+
f"in provided environment does not satisfy the Python version "
|
| 804 |
+
f"requirement {str(self.requires_python)!r}"
|
| 805 |
+
)
|
| 806 |
+
|
| 807 |
+
# #. If :ref:`pylock-environments` is specified, check that at least one of the
|
| 808 |
+
# environment marker expressions is satisfied; an error MUST be raised if no
|
| 809 |
+
# expression is satisfied.
|
| 810 |
+
if self.environments:
|
| 811 |
+
for env_marker in self.environments:
|
| 812 |
+
if env_marker.evaluate(
|
| 813 |
+
cast("dict[str, str]", environment or {}), context="requirement"
|
| 814 |
+
):
|
| 815 |
+
break
|
| 816 |
+
else:
|
| 817 |
+
raise PylockSelectError(
|
| 818 |
+
"Provided environment does not satisfy any of the "
|
| 819 |
+
"environments specified in the lock file"
|
| 820 |
+
)
|
| 821 |
+
|
| 822 |
+
# #. For each package listed in :ref:`pylock-packages`:
|
| 823 |
+
selected_packages_by_name: dict[str, tuple[int, Package]] = {}
|
| 824 |
+
for package_index, package in enumerate(self.packages):
|
| 825 |
+
# #. If :ref:`pylock-packages-marker` is specified, check if it is
|
| 826 |
+
# satisfied;if it isn't, skip to the next package.
|
| 827 |
+
if package.marker and not package.marker.evaluate(env, context="lock_file"):
|
| 828 |
+
continue
|
| 829 |
+
|
| 830 |
+
# #. If :ref:`pylock-packages-requires-python` is specified, check if it is
|
| 831 |
+
# satisfied; an error MUST be raised if it isn't.
|
| 832 |
+
if package.requires_python and not package.requires_python.contains(
|
| 833 |
+
env_python_full_version,
|
| 834 |
+
):
|
| 835 |
+
raise PylockSelectError(
|
| 836 |
+
f"python_full_version {env_python_full_version!r} "
|
| 837 |
+
f"in provided environment does not satisfy the Python version "
|
| 838 |
+
f"requirement {str(package.requires_python)!r} for package "
|
| 839 |
+
f"{package.name!r} at packages[{package_index}]"
|
| 840 |
+
)
|
| 841 |
+
|
| 842 |
+
# #. Check that no other conflicting instance of the package has been slated
|
| 843 |
+
# to be installed; an error about the ambiguity MUST be raised otherwise.
|
| 844 |
+
if package.name in selected_packages_by_name:
|
| 845 |
+
raise PylockSelectError(
|
| 846 |
+
f"Multiple packages with the name {package.name!r} are "
|
| 847 |
+
f"selected at packages[{package_index}] and "
|
| 848 |
+
f"packages[{selected_packages_by_name[package.name][0]}]"
|
| 849 |
+
)
|
| 850 |
+
|
| 851 |
+
# #. Check that the source of the package is specified appropriately (i.e.
|
| 852 |
+
# there are no conflicting sources in the package entry);
|
| 853 |
+
# an error MUST be raised if any issues are found.
|
| 854 |
+
# Covered by lock.validate() which is a precondition for this method.
|
| 855 |
+
|
| 856 |
+
# #. Add the package to the set of packages to install.
|
| 857 |
+
selected_packages_by_name[package.name] = (package_index, package)
|
| 858 |
+
|
| 859 |
+
# #. For each package to be installed:
|
| 860 |
+
for package_index, package in selected_packages_by_name.values():
|
| 861 |
+
# - If :ref:`pylock-packages-vcs` is set:
|
| 862 |
+
if package.vcs is not None:
|
| 863 |
+
yield package, package.vcs
|
| 864 |
+
|
| 865 |
+
# - Else if :ref:`pylock-packages-directory` is set:
|
| 866 |
+
elif package.directory is not None:
|
| 867 |
+
yield package, package.directory
|
| 868 |
+
|
| 869 |
+
# - Else if :ref:`pylock-packages-archive` is set:
|
| 870 |
+
elif package.archive is not None:
|
| 871 |
+
yield package, package.archive
|
| 872 |
+
|
| 873 |
+
# - Else if there are entries for :ref:`pylock-packages-wheels`:
|
| 874 |
+
elif package.wheels:
|
| 875 |
+
# #. Look for the appropriate wheel file based on
|
| 876 |
+
# :ref:`pylock-packages-wheels-name`; if one is not found then move
|
| 877 |
+
# on to :ref:`pylock-packages-sdist` or an error MUST be raised about
|
| 878 |
+
# a lack of source for the project.
|
| 879 |
+
best_wheel = next(
|
| 880 |
+
compatible_tags_selector(
|
| 881 |
+
(wheel, parse_wheel_filename(wheel.filename)[-1])
|
| 882 |
+
for wheel in package.wheels
|
| 883 |
+
),
|
| 884 |
+
None,
|
| 885 |
+
)
|
| 886 |
+
if best_wheel:
|
| 887 |
+
yield package, best_wheel
|
| 888 |
+
elif package.sdist is not None:
|
| 889 |
+
yield package, package.sdist
|
| 890 |
+
else:
|
| 891 |
+
raise PylockSelectError(
|
| 892 |
+
f"No wheel found matching the provided tags "
|
| 893 |
+
f"for package {package.name!r} "
|
| 894 |
+
f"at packages[{package_index}], "
|
| 895 |
+
f"and no sdist available as a fallback"
|
| 896 |
+
)
|
| 897 |
+
|
| 898 |
+
# - Else if no :ref:`pylock-packages-wheels` file is found or
|
| 899 |
+
# :ref:`pylock-packages-sdist` is solely set:
|
| 900 |
+
elif package.sdist is not None:
|
| 901 |
+
yield package, package.sdist
|
| 902 |
+
|
| 903 |
+
else:
|
| 904 |
+
# Covered by lock.validate() which is a precondition for this method.
|
| 905 |
+
raise NotImplementedError # pragma: no cover
|
LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/packaging/utils.py
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file is dual licensed under the terms of the Apache License, Version
|
| 2 |
+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
| 3 |
+
# for complete details.
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
|
| 7 |
+
import re
|
| 8 |
+
from typing import NewType, Tuple, Union, cast
|
| 9 |
+
|
| 10 |
+
from .tags import Tag, UnsortedTagsError, parse_tag
|
| 11 |
+
from .version import InvalidVersion, Version, _TrimmedRelease
|
| 12 |
+
|
| 13 |
+
__all__ = [
|
| 14 |
+
"BuildTag",
|
| 15 |
+
"InvalidName",
|
| 16 |
+
"InvalidSdistFilename",
|
| 17 |
+
"InvalidWheelFilename",
|
| 18 |
+
"NormalizedName",
|
| 19 |
+
"canonicalize_name",
|
| 20 |
+
"canonicalize_version",
|
| 21 |
+
"is_normalized_name",
|
| 22 |
+
"parse_sdist_filename",
|
| 23 |
+
"parse_wheel_filename",
|
| 24 |
+
]
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def __dir__() -> list[str]:
|
| 28 |
+
return __all__
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
BuildTag = Union[Tuple[()], Tuple[int, str]]
|
| 32 |
+
|
| 33 |
+
NormalizedName = NewType("NormalizedName", str)
|
| 34 |
+
"""
|
| 35 |
+
A :class:`typing.NewType` of :class:`str`, representing a normalized name.
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class InvalidName(ValueError):
|
| 40 |
+
"""
|
| 41 |
+
An invalid distribution name; users should refer to the packaging user guide.
|
| 42 |
+
"""
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
class InvalidWheelFilename(ValueError):
|
| 46 |
+
"""
|
| 47 |
+
An invalid wheel filename was found, users should refer to PEP 427.
|
| 48 |
+
"""
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class InvalidSdistFilename(ValueError):
|
| 52 |
+
"""
|
| 53 |
+
An invalid sdist filename was found, users should refer to the packaging user guide.
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
# Core metadata spec for `Name`
|
| 58 |
+
_validate_regex = re.compile(
|
| 59 |
+
r"[a-z0-9]|[a-z0-9][a-z0-9._-]*[a-z0-9]", re.IGNORECASE | re.ASCII
|
| 60 |
+
)
|
| 61 |
+
_normalized_regex = re.compile(r"[a-z0-9]|[a-z0-9]([a-z0-9-](?!--))*[a-z0-9]", re.ASCII)
|
| 62 |
+
# PEP 427: The build number must start with a digit.
|
| 63 |
+
_build_tag_regex = re.compile(r"(\d+)(.*)", re.ASCII)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def canonicalize_name(name: str, *, validate: bool = False) -> NormalizedName:
|
| 67 |
+
"""
|
| 68 |
+
This function takes a valid Python package or extra name, and returns the
|
| 69 |
+
normalized form of it.
|
| 70 |
+
|
| 71 |
+
The return type is typed as :class:`NormalizedName`. This allows type
|
| 72 |
+
checkers to help require that a string has passed through this function
|
| 73 |
+
before use.
|
| 74 |
+
|
| 75 |
+
If **validate** is true, then the function will check if **name** is a valid
|
| 76 |
+
distribution name before normalizing.
|
| 77 |
+
|
| 78 |
+
:param str name: The name to normalize.
|
| 79 |
+
:param bool validate: Check whether the name is a valid distribution name.
|
| 80 |
+
:raises InvalidName: If **validate** is true and the name is not an
|
| 81 |
+
acceptable distribution name.
|
| 82 |
+
|
| 83 |
+
>>> from packaging.utils import canonicalize_name
|
| 84 |
+
>>> canonicalize_name("Django")
|
| 85 |
+
'django'
|
| 86 |
+
>>> canonicalize_name("oslo.concurrency")
|
| 87 |
+
'oslo-concurrency'
|
| 88 |
+
>>> canonicalize_name("requests")
|
| 89 |
+
'requests'
|
| 90 |
+
"""
|
| 91 |
+
if validate and not _validate_regex.fullmatch(name):
|
| 92 |
+
raise InvalidName(f"name is invalid: {name!r}")
|
| 93 |
+
# Ensure all ``.`` and ``_`` are ``-``
|
| 94 |
+
# Emulates ``re.sub(r"[-_.]+", "-", name).lower()`` from PEP 503
|
| 95 |
+
# Much faster than re, and even faster than str.translate
|
| 96 |
+
value = name.lower().replace("_", "-").replace(".", "-")
|
| 97 |
+
# Condense repeats (faster than regex)
|
| 98 |
+
while "--" in value:
|
| 99 |
+
value = value.replace("--", "-")
|
| 100 |
+
return cast("NormalizedName", value)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def is_normalized_name(name: str) -> bool:
|
| 104 |
+
"""
|
| 105 |
+
Check if a name is already normalized (i.e. :func:`canonicalize_name` would
|
| 106 |
+
roundtrip to the same value).
|
| 107 |
+
|
| 108 |
+
:param str name: The name to check.
|
| 109 |
+
|
| 110 |
+
>>> from packaging.utils import is_normalized_name
|
| 111 |
+
>>> is_normalized_name("requests")
|
| 112 |
+
True
|
| 113 |
+
>>> is_normalized_name("Django")
|
| 114 |
+
False
|
| 115 |
+
"""
|
| 116 |
+
return _normalized_regex.fullmatch(name) is not None
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def canonicalize_version(
|
| 120 |
+
version: Version | str, *, strip_trailing_zero: bool = True
|
| 121 |
+
) -> str:
|
| 122 |
+
"""Return a canonical form of a version as a string.
|
| 123 |
+
|
| 124 |
+
This function takes a string representing a package version (or a
|
| 125 |
+
:class:`~packaging.version.Version` instance), and returns the
|
| 126 |
+
normalized form of it. By default, it strips trailing zeros from
|
| 127 |
+
the release segment.
|
| 128 |
+
|
| 129 |
+
>>> from packaging.utils import canonicalize_version
|
| 130 |
+
>>> canonicalize_version('1.0.1')
|
| 131 |
+
'1.0.1'
|
| 132 |
+
|
| 133 |
+
Per PEP 625, versions may have multiple canonical forms, differing
|
| 134 |
+
only by trailing zeros.
|
| 135 |
+
|
| 136 |
+
>>> canonicalize_version('1.0.0')
|
| 137 |
+
'1'
|
| 138 |
+
>>> canonicalize_version('1.0.0', strip_trailing_zero=False)
|
| 139 |
+
'1.0.0'
|
| 140 |
+
|
| 141 |
+
Invalid versions are returned unaltered.
|
| 142 |
+
|
| 143 |
+
>>> canonicalize_version('foo bar baz')
|
| 144 |
+
'foo bar baz'
|
| 145 |
+
|
| 146 |
+
>>> canonicalize_version('1.4.0.0.0')
|
| 147 |
+
'1.4'
|
| 148 |
+
"""
|
| 149 |
+
if isinstance(version, str):
|
| 150 |
+
try:
|
| 151 |
+
version = Version(version)
|
| 152 |
+
except InvalidVersion:
|
| 153 |
+
return str(version)
|
| 154 |
+
return str(_TrimmedRelease(version) if strip_trailing_zero else version)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def parse_wheel_filename(
|
| 158 |
+
filename: str,
|
| 159 |
+
*,
|
| 160 |
+
validate_order: bool = False,
|
| 161 |
+
) -> tuple[NormalizedName, Version, BuildTag, frozenset[Tag]]:
|
| 162 |
+
"""
|
| 163 |
+
This function takes the filename of a wheel file, and parses it,
|
| 164 |
+
returning a tuple of name, version, build number, and tags.
|
| 165 |
+
|
| 166 |
+
The name part of the tuple is normalized and typed as
|
| 167 |
+
:class:`NormalizedName`. The version portion is an instance of
|
| 168 |
+
:class:`~packaging.version.Version`. The build number is ``()`` if
|
| 169 |
+
there is no build number in the wheel filename, otherwise a
|
| 170 |
+
two-item tuple of an integer for the leading digits and
|
| 171 |
+
a string for the rest of the build number. The tags portion is a
|
| 172 |
+
frozen set of :class:`~packaging.tags.Tag` instances (as the tag
|
| 173 |
+
string format allows multiple tags to be combined into a single
|
| 174 |
+
string).
|
| 175 |
+
|
| 176 |
+
If **validate_order** is true, compressed tag set components are
|
| 177 |
+
checked to be in sorted order as required by PEP 425.
|
| 178 |
+
|
| 179 |
+
:param str filename: The name of the wheel file.
|
| 180 |
+
:param bool validate_order: Check whether compressed tag set components
|
| 181 |
+
are in sorted order.
|
| 182 |
+
:raises InvalidWheelFilename: If the filename in question
|
| 183 |
+
does not follow the :ref:`wheel specification
|
| 184 |
+
<pypug:binary-distribution-format>`.
|
| 185 |
+
|
| 186 |
+
>>> from packaging.utils import parse_wheel_filename
|
| 187 |
+
>>> from packaging.tags import Tag
|
| 188 |
+
>>> from packaging.version import Version
|
| 189 |
+
>>> name, ver, build, tags = parse_wheel_filename("foo-1.0-py3-none-any.whl")
|
| 190 |
+
>>> name
|
| 191 |
+
'foo'
|
| 192 |
+
>>> ver == Version('1.0')
|
| 193 |
+
True
|
| 194 |
+
>>> tags == {Tag("py3", "none", "any")}
|
| 195 |
+
True
|
| 196 |
+
>>> not build
|
| 197 |
+
True
|
| 198 |
+
|
| 199 |
+
.. versionadded:: 26.1
|
| 200 |
+
The *validate_order* parameter.
|
| 201 |
+
"""
|
| 202 |
+
if not filename.endswith(".whl"):
|
| 203 |
+
raise InvalidWheelFilename(
|
| 204 |
+
f"Invalid wheel filename (extension must be '.whl'): {filename!r}"
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
+
filename = filename[:-4]
|
| 208 |
+
dashes = filename.count("-")
|
| 209 |
+
if dashes not in (4, 5):
|
| 210 |
+
raise InvalidWheelFilename(
|
| 211 |
+
f"Invalid wheel filename (wrong number of parts): {filename!r}"
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
parts = filename.split("-", dashes - 2)
|
| 215 |
+
name_part = parts[0]
|
| 216 |
+
# See PEP 427 for the rules on escaping the project name.
|
| 217 |
+
if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None:
|
| 218 |
+
raise InvalidWheelFilename(f"Invalid project name: {filename!r}")
|
| 219 |
+
name = canonicalize_name(name_part)
|
| 220 |
+
|
| 221 |
+
try:
|
| 222 |
+
version = Version(parts[1])
|
| 223 |
+
except InvalidVersion as e:
|
| 224 |
+
raise InvalidWheelFilename(
|
| 225 |
+
f"Invalid wheel filename (invalid version): {filename!r}"
|
| 226 |
+
) from e
|
| 227 |
+
|
| 228 |
+
if dashes == 5:
|
| 229 |
+
build_part = parts[2]
|
| 230 |
+
build_match = _build_tag_regex.match(build_part)
|
| 231 |
+
if build_match is None:
|
| 232 |
+
raise InvalidWheelFilename(
|
| 233 |
+
f"Invalid build number: {build_part} in {filename!r}"
|
| 234 |
+
)
|
| 235 |
+
build = cast("BuildTag", (int(build_match.group(1)), build_match.group(2)))
|
| 236 |
+
else:
|
| 237 |
+
build = ()
|
| 238 |
+
tag_str = parts[-1]
|
| 239 |
+
try:
|
| 240 |
+
tags = parse_tag(tag_str, validate_order=validate_order)
|
| 241 |
+
except UnsortedTagsError:
|
| 242 |
+
raise InvalidWheelFilename(
|
| 243 |
+
f"Invalid wheel filename (compressed tag set components must be in "
|
| 244 |
+
f"sorted order per PEP 425): {filename!r}"
|
| 245 |
+
) from None
|
| 246 |
+
return (name, version, build, tags)
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def parse_sdist_filename(filename: str) -> tuple[NormalizedName, Version]:
|
| 250 |
+
"""
|
| 251 |
+
This function takes the filename of a sdist file (as specified
|
| 252 |
+
in the `Source distribution format`_ documentation), and parses
|
| 253 |
+
it, returning a tuple of the normalized name and version as
|
| 254 |
+
represented by an instance of :class:`~packaging.version.Version`.
|
| 255 |
+
|
| 256 |
+
:param str filename: The name of the sdist file.
|
| 257 |
+
:raises InvalidSdistFilename: If the filename does not end
|
| 258 |
+
with an sdist extension (``.zip`` or ``.tar.gz``), or if it does not
|
| 259 |
+
contain a dash separating the name and the version of the distribution.
|
| 260 |
+
|
| 261 |
+
>>> from packaging.utils import parse_sdist_filename
|
| 262 |
+
>>> from packaging.version import Version
|
| 263 |
+
>>> name, ver = parse_sdist_filename("foo-1.0.tar.gz")
|
| 264 |
+
>>> name
|
| 265 |
+
'foo'
|
| 266 |
+
>>> ver == Version('1.0')
|
| 267 |
+
True
|
| 268 |
+
|
| 269 |
+
.. _Source distribution format: https://packaging.python.org/specifications/source-distribution-format/#source-distribution-file-name
|
| 270 |
+
"""
|
| 271 |
+
if filename.endswith(".tar.gz"):
|
| 272 |
+
file_stem = filename[: -len(".tar.gz")]
|
| 273 |
+
elif filename.endswith(".zip"):
|
| 274 |
+
file_stem = filename[: -len(".zip")]
|
| 275 |
+
else:
|
| 276 |
+
raise InvalidSdistFilename(
|
| 277 |
+
f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):"
|
| 278 |
+
f" {filename!r}"
|
| 279 |
+
)
|
| 280 |
+
|
| 281 |
+
# We are requiring a PEP 440 version, which cannot contain dashes,
|
| 282 |
+
# so we split on the last dash.
|
| 283 |
+
name_part, sep, version_part = file_stem.rpartition("-")
|
| 284 |
+
if not sep:
|
| 285 |
+
raise InvalidSdistFilename(f"Invalid sdist filename: {filename!r}")
|
| 286 |
+
|
| 287 |
+
name = canonicalize_name(name_part)
|
| 288 |
+
|
| 289 |
+
try:
|
| 290 |
+
version = Version(version_part)
|
| 291 |
+
except InvalidVersion as e:
|
| 292 |
+
raise InvalidSdistFilename(
|
| 293 |
+
f"Invalid sdist filename (invalid version): {filename!r}"
|
| 294 |
+
) from e
|
| 295 |
+
|
| 296 |
+
return (name, version)
|
LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/cohere_asr/feature_extraction_cohere_asr.py
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2025 The HuggingFace Inc. team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import numpy as np
|
| 16 |
+
import torch
|
| 17 |
+
|
| 18 |
+
from ...feature_extraction_sequence_utils import SequenceFeatureExtractor
|
| 19 |
+
from ...feature_extraction_utils import BatchFeature
|
| 20 |
+
from ...utils import TensorType, is_librosa_available, logging
|
| 21 |
+
from ...utils.import_utils import requires
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
if is_librosa_available():
|
| 25 |
+
import librosa
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
EPSILON = 1e-5
|
| 29 |
+
LOG_ZERO_GUARD_VALUE = 2**-24
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
logger = logging.get_logger(__name__)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
@requires(backends=("torch", "librosa"))
|
| 36 |
+
class CohereAsrFeatureExtractor(SequenceFeatureExtractor):
|
| 37 |
+
r"""
|
| 38 |
+
Constructs a CohereAsr feature extractor.
|
| 39 |
+
|
| 40 |
+
This feature extractor inherits from [`~feature_extraction_sequence_utils.SequenceFeatureExtractor`] which contains
|
| 41 |
+
most of the main methods. Users should refer to this superclass for more information regarding those methods.
|
| 42 |
+
|
| 43 |
+
This class extracts mel-filter bank features from raw speech using a custom numpy implementation of the `Short Time
|
| 44 |
+
Fourier Transform` which should match pytorch's `torch.stft` equivalent.
|
| 45 |
+
|
| 46 |
+
Args:
|
| 47 |
+
feature_size (`int`, *optional*, defaults to 128):
|
| 48 |
+
The feature dimension of the extracted features.
|
| 49 |
+
sampling_rate (`int`, *optional*, defaults to 16000):
|
| 50 |
+
The sampling rate at which the audio files should be digitalized expressed in hertz (Hz).
|
| 51 |
+
hop_length (`int`, *optional*, defaults to 160):
|
| 52 |
+
Length of the overlapping windows for the STFT used to obtain the Mel Frequency coefficients.
|
| 53 |
+
n_fft (`int`, *optional*, defaults to 512):
|
| 54 |
+
Size of the Fourier transform.
|
| 55 |
+
win_length (`int`, *optional*, defaults to 400):
|
| 56 |
+
The window length for the STFT computation.
|
| 57 |
+
preemphasis (`float`, *optional*, defaults to 0.97):
|
| 58 |
+
A preemphasis filter coefficient. 0.0 means no preemphasis filter.
|
| 59 |
+
padding_value (`float`, *optional*, defaults to 0.0):
|
| 60 |
+
Padding value used to pad the audio. Should correspond to silences.
|
| 61 |
+
dither (`float`, *optional*, defaults to 1e-05):
|
| 62 |
+
Amount of deterministic dither noise to add before feature extraction. Each sample is seeded by its
|
| 63 |
+
valid waveform length so that dither is batch-composition invariant. Set to 0.0 to disable.
|
| 64 |
+
max_audio_clip_s (`float`, *optional*, defaults to 35.0):
|
| 65 |
+
Maximum duration in seconds for a single audio chunk. Audio longer than
|
| 66 |
+
`max_audio_clip_s - overlap_chunk_second` is split at energy-based boundaries.
|
| 67 |
+
overlap_chunk_second (`float`, *optional*, defaults to 5.0):
|
| 68 |
+
Size in seconds of the boundary search window used when splitting long audio. This is not actual
|
| 69 |
+
overlap between chunks — it defines how far back from the chunk boundary to search for a quiet
|
| 70 |
+
split point.
|
| 71 |
+
min_energy_window_samples (`int`, *optional*, defaults to 1600):
|
| 72 |
+
Size in samples of the sliding window used to find the quietest point when splitting audio chunks.
|
| 73 |
+
"""
|
| 74 |
+
|
| 75 |
+
model_input_names = ["input_features", "attention_mask"]
|
| 76 |
+
|
| 77 |
+
def __init__(
|
| 78 |
+
self,
|
| 79 |
+
feature_size=128,
|
| 80 |
+
sampling_rate=16000,
|
| 81 |
+
hop_length=160,
|
| 82 |
+
n_fft=512,
|
| 83 |
+
win_length=400,
|
| 84 |
+
preemphasis=0.97,
|
| 85 |
+
padding_value=0.0,
|
| 86 |
+
dither=1e-5,
|
| 87 |
+
max_audio_clip_s=35.0,
|
| 88 |
+
overlap_chunk_second=5.0,
|
| 89 |
+
min_energy_window_samples=1600,
|
| 90 |
+
**kwargs,
|
| 91 |
+
):
|
| 92 |
+
super().__init__(feature_size=feature_size, sampling_rate=sampling_rate, padding_value=padding_value, **kwargs)
|
| 93 |
+
|
| 94 |
+
self.hop_length = hop_length
|
| 95 |
+
self.n_fft = n_fft
|
| 96 |
+
self.win_length = win_length
|
| 97 |
+
self.preemphasis = preemphasis
|
| 98 |
+
self.dither = dither
|
| 99 |
+
self.max_audio_clip_s = max_audio_clip_s
|
| 100 |
+
self.overlap_chunk_second = overlap_chunk_second
|
| 101 |
+
self.min_energy_window_samples = min_energy_window_samples
|
| 102 |
+
|
| 103 |
+
# TODO: @eustlb, for now we use librosa to compute the mel filters
|
| 104 |
+
# indeed mel_filter_bank uses np.float64 (while librosa uses np.float32), giving numerical differences
|
| 105 |
+
mel_filters = librosa.filters.mel(
|
| 106 |
+
sr=sampling_rate, n_fft=n_fft, n_mels=feature_size, fmin=0.0, fmax=sampling_rate / 2, norm="slaney"
|
| 107 |
+
)
|
| 108 |
+
self.mel_filters = torch.from_numpy(mel_filters).to(torch.float32)
|
| 109 |
+
|
| 110 |
+
def _find_split_point_energy(self, waveform: torch.Tensor, start_idx: int, end_idx: int) -> int:
|
| 111 |
+
segment = waveform[start_idx:end_idx]
|
| 112 |
+
if segment.shape[0] <= self.min_energy_window_samples:
|
| 113 |
+
return (start_idx + end_idx) // 2
|
| 114 |
+
|
| 115 |
+
min_energy = float("inf")
|
| 116 |
+
quietest_idx = start_idx
|
| 117 |
+
upper = segment.shape[0] - self.min_energy_window_samples
|
| 118 |
+
for i in range(0, upper, self.min_energy_window_samples):
|
| 119 |
+
window = segment[i : i + self.min_energy_window_samples]
|
| 120 |
+
energy = torch.sqrt(torch.mean(window * window)).item()
|
| 121 |
+
if energy < min_energy:
|
| 122 |
+
min_energy = energy
|
| 123 |
+
quietest_idx = start_idx + i
|
| 124 |
+
return quietest_idx
|
| 125 |
+
|
| 126 |
+
def _split_audio_chunks_energy(self, waveform: torch.Tensor) -> list[torch.Tensor]:
|
| 127 |
+
chunk_size = max(1, int(round(self.max_audio_clip_s * self.sampling_rate)))
|
| 128 |
+
boundary_context_size = max(1, int(round(self.overlap_chunk_second * self.sampling_rate)))
|
| 129 |
+
total_samples = waveform.shape[0]
|
| 130 |
+
|
| 131 |
+
if total_samples <= chunk_size:
|
| 132 |
+
return [waveform]
|
| 133 |
+
|
| 134 |
+
chunks_meta: list[tuple[int, int]] = []
|
| 135 |
+
idx = 0
|
| 136 |
+
while idx < total_samples:
|
| 137 |
+
if idx + chunk_size >= total_samples:
|
| 138 |
+
chunks_meta.append((idx, total_samples))
|
| 139 |
+
break
|
| 140 |
+
|
| 141 |
+
search_start = max(idx, idx + chunk_size - boundary_context_size)
|
| 142 |
+
search_end = min(idx + chunk_size, total_samples)
|
| 143 |
+
if search_end <= search_start:
|
| 144 |
+
split_point = idx + chunk_size
|
| 145 |
+
else:
|
| 146 |
+
split_point = self._find_split_point_energy(waveform, search_start, search_end)
|
| 147 |
+
|
| 148 |
+
split_point = max(idx + 1, min(split_point, total_samples))
|
| 149 |
+
chunks_meta.append((idx, split_point))
|
| 150 |
+
idx = split_point
|
| 151 |
+
|
| 152 |
+
return [waveform[start:end] for start, end in chunks_meta if end > start]
|
| 153 |
+
|
| 154 |
+
def _apply_dither(self, waveform: torch.Tensor, audio_lengths: torch.Tensor) -> torch.Tensor:
|
| 155 |
+
if self.dither <= 0:
|
| 156 |
+
return waveform
|
| 157 |
+
generator = torch.Generator(device=waveform.device)
|
| 158 |
+
for i in range(waveform.shape[0]):
|
| 159 |
+
valid_samples = min(int(audio_lengths[i].item()), waveform.shape[1])
|
| 160 |
+
if valid_samples <= 0:
|
| 161 |
+
continue
|
| 162 |
+
generator.manual_seed(valid_samples)
|
| 163 |
+
noise = torch.randn(valid_samples, dtype=waveform.dtype, device=waveform.device, generator=generator)
|
| 164 |
+
waveform[i, :valid_samples] += self.dither * noise
|
| 165 |
+
return waveform
|
| 166 |
+
|
| 167 |
+
def _torch_extract_fbank_features(self, waveform, device="cpu"):
|
| 168 |
+
# spectrogram
|
| 169 |
+
window = torch.hann_window(self.win_length, periodic=False, device=device)
|
| 170 |
+
stft = torch.stft(
|
| 171 |
+
waveform,
|
| 172 |
+
self.n_fft,
|
| 173 |
+
hop_length=self.hop_length,
|
| 174 |
+
win_length=self.win_length,
|
| 175 |
+
window=window,
|
| 176 |
+
return_complex=True,
|
| 177 |
+
pad_mode="constant",
|
| 178 |
+
)
|
| 179 |
+
# Let's match original implementation
|
| 180 |
+
magnitudes = torch.view_as_real(stft)
|
| 181 |
+
magnitudes = torch.sqrt(magnitudes.pow(2).sum(-1))
|
| 182 |
+
magnitudes = magnitudes.pow(2)
|
| 183 |
+
|
| 184 |
+
# log mel spectrogram
|
| 185 |
+
mel_filters = self.mel_filters.to(device)
|
| 186 |
+
mel_spec = mel_filters @ magnitudes
|
| 187 |
+
mel_spec = torch.log(mel_spec + LOG_ZERO_GUARD_VALUE)
|
| 188 |
+
|
| 189 |
+
# (batch_size, num_mel_filters, num_frames) -> (batch_size, num_frames, num_mel_filters)
|
| 190 |
+
mel_spec = mel_spec.permute(0, 2, 1)
|
| 191 |
+
|
| 192 |
+
return mel_spec
|
| 193 |
+
|
| 194 |
+
def __call__(
|
| 195 |
+
self,
|
| 196 |
+
raw_speech: np.ndarray | list[float] | list[np.ndarray] | list[list[float]],
|
| 197 |
+
truncation: bool = False,
|
| 198 |
+
pad_to_multiple_of: int | None = None,
|
| 199 |
+
return_tensors: str | TensorType | None = None,
|
| 200 |
+
return_attention_mask: bool | None = None,
|
| 201 |
+
padding: str | None = "longest",
|
| 202 |
+
max_length: int | None = None,
|
| 203 |
+
sampling_rate: int | None = None,
|
| 204 |
+
do_normalize: bool | None = None,
|
| 205 |
+
device: str | None = "cpu",
|
| 206 |
+
return_token_timestamps: bool | None = None,
|
| 207 |
+
**kwargs,
|
| 208 |
+
) -> BatchFeature:
|
| 209 |
+
"""
|
| 210 |
+
Main method to featurize and prepare for the model one or several sequence(s). Implementation uses PyTorch for
|
| 211 |
+
the STFT computation if available, otherwise a slower NumPy based one.
|
| 212 |
+
|
| 213 |
+
Args:
|
| 214 |
+
raw_speech (`np.ndarray`, `list[float]`, `list[np.ndarray]`, `list[list[float]]`):
|
| 215 |
+
The sequence or batch of sequences to be padded. Each sequence can be a numpy array, a list of float
|
| 216 |
+
values, a list of numpy arrays or a list of list of float values. Must be mono channel audio, not
|
| 217 |
+
stereo, i.e. single float per timestep.
|
| 218 |
+
truncation (`bool`, *optional*, default to `True`):
|
| 219 |
+
Activates truncation to cut input sequences longer than *max_length* to *max_length*.
|
| 220 |
+
pad_to_multiple_of (`int`, *optional*, defaults to None):
|
| 221 |
+
If set will pad the sequence to a multiple of the provided value.
|
| 222 |
+
|
| 223 |
+
This is especially useful to enable the use of Tensor Cores on NVIDIA hardware with compute capability
|
| 224 |
+
`>= 7.5` (Volta), or on TPUs which benefit from having sequence lengths be a multiple of 128.
|
| 225 |
+
return_attention_mask (`bool`, *optional*):
|
| 226 |
+
Whether to return the attention mask. If left to the default, will return the attention mask according
|
| 227 |
+
to the specific feature_extractor's default.
|
| 228 |
+
|
| 229 |
+
[What are attention masks?](../glossary#attention-mask)
|
| 230 |
+
|
| 231 |
+
<Tip>
|
| 232 |
+
|
| 233 |
+
For CohereAsr models, `attention_mask` should always be passed for batched inference, to avoid subtle
|
| 234 |
+
bugs.
|
| 235 |
+
|
| 236 |
+
</Tip>
|
| 237 |
+
|
| 238 |
+
return_tensors (`str` or [`~utils.TensorType`], *optional*):
|
| 239 |
+
If set, will return tensors instead of list of python integers. Acceptable values are:
|
| 240 |
+
|
| 241 |
+
- `'tf'`: Return TensorFlow `tf.constant` objects.
|
| 242 |
+
- `'pt'`: Return PyTorch `torch.Tensor` objects.
|
| 243 |
+
- `'np'`: Return Numpy `np.ndarray` objects.
|
| 244 |
+
sampling_rate (`int`, *optional*):
|
| 245 |
+
The sampling rate at which the `raw_speech` input was sampled. It is strongly recommended to pass
|
| 246 |
+
`sampling_rate` at the forward call to prevent silent errors and allow automatic speech recognition
|
| 247 |
+
pipeline.
|
| 248 |
+
padding_value (`float`, *optional*, defaults to 0.0):
|
| 249 |
+
The value that is used to fill the padding values / vectors.
|
| 250 |
+
do_normalize (`bool`, *optional*, defaults to `False`):
|
| 251 |
+
Whether or not to zero-mean unit-variance normalize the input. Normalizing can help to significantly
|
| 252 |
+
improve the performance of the model.
|
| 253 |
+
device (`str`, *optional*, defaults to `'cpu'`):
|
| 254 |
+
Specifies the device for computation of the log-mel spectrogram of audio signals in the
|
| 255 |
+
`_torch_extract_fbank_features` method. (e.g., "cpu", "cuda")
|
| 256 |
+
return_token_timestamps (`bool`, *optional*, defaults to `None`):
|
| 257 |
+
Deprecated. Use `return_attention_mask` instead from which the number of frames can be inferred.
|
| 258 |
+
|
| 259 |
+
Whether or not to return the number of frames of the input raw_speech.
|
| 260 |
+
These num_frames can be used by the model to compute word level timestamps.
|
| 261 |
+
"""
|
| 262 |
+
if sampling_rate is not None:
|
| 263 |
+
if sampling_rate != self.sampling_rate:
|
| 264 |
+
raise ValueError(
|
| 265 |
+
f"The model corresponding to this feature extractor: {self.__class__.__name__} was trained using a"
|
| 266 |
+
f" sampling rate of {self.sampling_rate}. Please make sure that the provided `raw_speech` input"
|
| 267 |
+
f" was sampled with {self.sampling_rate} and not {sampling_rate}."
|
| 268 |
+
)
|
| 269 |
+
else:
|
| 270 |
+
logger.warning(
|
| 271 |
+
f"It is strongly recommended to pass the `sampling_rate` argument to `{self.__class__.__name__}()`. "
|
| 272 |
+
"Failing to do so can result in silent errors that might be hard to debug."
|
| 273 |
+
)
|
| 274 |
+
|
| 275 |
+
# Convert to torch tensor
|
| 276 |
+
if isinstance(raw_speech, np.ndarray):
|
| 277 |
+
raw_speech = torch.tensor(raw_speech)
|
| 278 |
+
elif isinstance(raw_speech, (list, tuple)) and isinstance(raw_speech[0], np.ndarray):
|
| 279 |
+
raw_speech = [torch.tensor(speech) for speech in raw_speech]
|
| 280 |
+
|
| 281 |
+
is_batched_torch = isinstance(raw_speech, torch.Tensor) and len(raw_speech.shape) > 1
|
| 282 |
+
if is_batched_torch and len(raw_speech.shape) > 2:
|
| 283 |
+
logger.warning(
|
| 284 |
+
f"Only mono-channel audio is supported for input to {self.__class__.__name__}. "
|
| 285 |
+
"We will take the mean of the channels to convert to mono."
|
| 286 |
+
)
|
| 287 |
+
raw_speech = raw_speech.mean(-1)
|
| 288 |
+
|
| 289 |
+
is_batched_sequence = isinstance(raw_speech, (list, tuple))
|
| 290 |
+
if is_batched_sequence:
|
| 291 |
+
for speech in raw_speech:
|
| 292 |
+
if len(speech.shape) > 1:
|
| 293 |
+
logger.warning(
|
| 294 |
+
f"Only mono-channel audio is supported for input to {self.__class__.__name__}. "
|
| 295 |
+
"We will take the mean of the channels to convert to mono."
|
| 296 |
+
)
|
| 297 |
+
speech = speech.mean(-1)
|
| 298 |
+
|
| 299 |
+
if is_batched_torch or is_batched_sequence:
|
| 300 |
+
raw_speech = [speech.to(torch.float32) for speech in raw_speech]
|
| 301 |
+
else:
|
| 302 |
+
raw_speech = [raw_speech.to(torch.float32)]
|
| 303 |
+
|
| 304 |
+
# Chunk long audio at energy-based boundaries
|
| 305 |
+
fast_path_threshold_s = max(0.0, self.max_audio_clip_s - self.overlap_chunk_second)
|
| 306 |
+
audio_chunk_index: list[tuple[int, int | None]] = []
|
| 307 |
+
chunked_speech: list[torch.Tensor] = []
|
| 308 |
+
for sample_idx, speech in enumerate(raw_speech):
|
| 309 |
+
duration_s = speech.shape[0] / self.sampling_rate
|
| 310 |
+
if duration_s <= fast_path_threshold_s:
|
| 311 |
+
chunked_speech.append(speech)
|
| 312 |
+
audio_chunk_index.append((sample_idx, None))
|
| 313 |
+
else:
|
| 314 |
+
chunks = self._split_audio_chunks_energy(speech)
|
| 315 |
+
for chunk_idx, chunk in enumerate(chunks):
|
| 316 |
+
chunked_speech.append(chunk)
|
| 317 |
+
audio_chunk_index.append((sample_idx, chunk_idx))
|
| 318 |
+
|
| 319 |
+
raw_speech = [speech[:, None] for speech in chunked_speech]
|
| 320 |
+
|
| 321 |
+
audio_lengths = [len(speech) for speech in raw_speech]
|
| 322 |
+
batched_speech = BatchFeature({"input_features": raw_speech, "audio_lengths": audio_lengths})
|
| 323 |
+
|
| 324 |
+
padded_inputs = self.pad(
|
| 325 |
+
batched_speech,
|
| 326 |
+
padding=padding,
|
| 327 |
+
max_length=max_length,
|
| 328 |
+
truncation=truncation,
|
| 329 |
+
pad_to_multiple_of=pad_to_multiple_of,
|
| 330 |
+
return_tensors="pt",
|
| 331 |
+
)
|
| 332 |
+
input_features = padded_inputs.input_features.squeeze(-1)
|
| 333 |
+
|
| 334 |
+
# dithering
|
| 335 |
+
input_features = self._apply_dither(input_features, padded_inputs.audio_lengths)
|
| 336 |
+
|
| 337 |
+
# preemphasis
|
| 338 |
+
if self.preemphasis is not None:
|
| 339 |
+
timemask = torch.arange(input_features.shape[1], device=input_features.device).unsqueeze(
|
| 340 |
+
0
|
| 341 |
+
) < padded_inputs.audio_lengths.unsqueeze(1)
|
| 342 |
+
input_features = torch.cat(
|
| 343 |
+
[input_features[:, :1], input_features[:, 1:] - self.preemphasis * input_features[:, :-1]], dim=1
|
| 344 |
+
)
|
| 345 |
+
input_features = input_features.masked_fill(~timemask, 0.0)
|
| 346 |
+
|
| 347 |
+
input_features = self._torch_extract_fbank_features(input_features, device)
|
| 348 |
+
features_lengths = torch.floor_divide(
|
| 349 |
+
padded_inputs.audio_lengths + self.n_fft // 2 * 2 - self.n_fft, self.hop_length
|
| 350 |
+
)
|
| 351 |
+
attention_mask = torch.arange(input_features.shape[1], device=device)[None, :] < features_lengths[:, None]
|
| 352 |
+
|
| 353 |
+
# normalize mel features, ignoring padding
|
| 354 |
+
mask = attention_mask.unsqueeze(-1)
|
| 355 |
+
input_features_masked = input_features * mask
|
| 356 |
+
mean = input_features_masked.sum(dim=1) / features_lengths.unsqueeze(-1)
|
| 357 |
+
mean = mean.unsqueeze(1)
|
| 358 |
+
variance = ((input_features_masked - mean) ** 2 * mask).sum(dim=1) / (features_lengths - 1).unsqueeze(-1)
|
| 359 |
+
std = torch.sqrt(variance).unsqueeze(1)
|
| 360 |
+
input_features = (input_features - mean) / (std + EPSILON)
|
| 361 |
+
input_features *= mask
|
| 362 |
+
|
| 363 |
+
result = BatchFeature(
|
| 364 |
+
data={
|
| 365 |
+
"input_features": input_features,
|
| 366 |
+
"attention_mask": attention_mask,
|
| 367 |
+
},
|
| 368 |
+
tensor_type=return_tensors,
|
| 369 |
+
)
|
| 370 |
+
result["audio_chunk_index"] = audio_chunk_index
|
| 371 |
+
return result
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
__all__ = ["CohereAsrFeatureExtractor"]
|
LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/cohere_asr/processing_cohere_asr.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 The HuggingFace Inc. team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from ...audio_utils import AudioInput, make_list_of_audio
|
| 16 |
+
from ...processing_utils import ProcessingKwargs, ProcessorMixin, Unpack
|
| 17 |
+
from ...tokenization_utils_base import PreTokenizedInput, TextInput
|
| 18 |
+
from ...utils import auto_docstring, is_torch_available, logging
|
| 19 |
+
from ...utils.import_utils import requires
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
if is_torch_available():
|
| 23 |
+
import torch
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
LANGUAGES = {"ar", "de", "el", "en", "es", "fr", "it", "ja", "ko", "nl", "pl", "pt", "vi", "zh"}
|
| 27 |
+
_NO_SPACE_LANGS = {"ja", "zh"}
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
logger = logging.get_logger(__name__)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class CohereAsrProcessorKwargs(ProcessingKwargs, total=False):
|
| 34 |
+
_defaults = {
|
| 35 |
+
"audio_kwargs": {
|
| 36 |
+
"sampling_rate": 16000,
|
| 37 |
+
"padding": "longest",
|
| 38 |
+
"return_attention_mask": True,
|
| 39 |
+
},
|
| 40 |
+
"text_kwargs": {
|
| 41 |
+
"padding": True,
|
| 42 |
+
"padding_side": "right",
|
| 43 |
+
"add_special_tokens": False,
|
| 44 |
+
},
|
| 45 |
+
"common_kwargs": {"return_tensors": "pt"},
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
@auto_docstring
|
| 50 |
+
@requires(backends=("torch",))
|
| 51 |
+
class CohereAsrProcessor(ProcessorMixin):
|
| 52 |
+
def __init__(self, feature_extractor, tokenizer):
|
| 53 |
+
super().__init__(feature_extractor, tokenizer)
|
| 54 |
+
|
| 55 |
+
def get_decoder_prompt_ids(self, language: str, punctuation: bool = True) -> list[int]:
|
| 56 |
+
"""Build the decoder prompt token IDs for the given language and punctuation settings."""
|
| 57 |
+
if language not in LANGUAGES:
|
| 58 |
+
raise ValueError(
|
| 59 |
+
f"Unsupported language: {language!r}. Supported languages: {', '.join(sorted(LANGUAGES))}."
|
| 60 |
+
)
|
| 61 |
+
pnc_token = "<|pnc|>" if punctuation else "<|nopnc|>"
|
| 62 |
+
tokens = [
|
| 63 |
+
"▁",
|
| 64 |
+
"<|startofcontext|>",
|
| 65 |
+
"<|startoftranscript|>",
|
| 66 |
+
"<|emo:undefined|>",
|
| 67 |
+
f"<|{language}|>",
|
| 68 |
+
f"<|{language}|>",
|
| 69 |
+
pnc_token,
|
| 70 |
+
"<|noitn|>",
|
| 71 |
+
"<|notimestamp|>",
|
| 72 |
+
"<|nodiarize|>",
|
| 73 |
+
]
|
| 74 |
+
return self.tokenizer.convert_tokens_to_ids(tokens)
|
| 75 |
+
|
| 76 |
+
@auto_docstring
|
| 77 |
+
def __call__(
|
| 78 |
+
self,
|
| 79 |
+
audio: AudioInput,
|
| 80 |
+
language: str,
|
| 81 |
+
text: TextInput | PreTokenizedInput | list[TextInput] | list[PreTokenizedInput] | None = None,
|
| 82 |
+
punctuation: bool = True,
|
| 83 |
+
sampling_rate: int | None = None,
|
| 84 |
+
**kwargs: Unpack[CohereAsrProcessorKwargs],
|
| 85 |
+
):
|
| 86 |
+
r"""
|
| 87 |
+
language (`str`):
|
| 88 |
+
Language code (e.g. `"en"`, `"es"`, `"fr"`) used to build the decoder prompt. The processor
|
| 89 |
+
constructs the full decoder prompt and returns `decoder_input_ids` alongside the audio features.
|
| 90 |
+
punctuation (`bool`, defaults to `True`):
|
| 91 |
+
Whether to enable punctuation in the decoder prompt.
|
| 92 |
+
sampling_rate (`int`, *optional*):
|
| 93 |
+
The sampling rate of the input audio in Hz. This should match the sampling rate expected by the feature
|
| 94 |
+
extractor (defaults to 16000 Hz). If provided, it will be validated against the processor's expected
|
| 95 |
+
sampling rate, and an error will be raised if they don't match. If not provided, a warning will be
|
| 96 |
+
issued and the default sampling rate will be assumed.
|
| 97 |
+
"""
|
| 98 |
+
audio = make_list_of_audio(audio)
|
| 99 |
+
|
| 100 |
+
output_kwargs = self._merge_kwargs(
|
| 101 |
+
CohereAsrProcessorKwargs,
|
| 102 |
+
tokenizer_init_kwargs=self.tokenizer.init_kwargs,
|
| 103 |
+
**kwargs,
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
if sampling_rate is None:
|
| 107 |
+
logger.warning_once(
|
| 108 |
+
f"You've provided audio without specifying the sampling rate. It will be assumed to be {output_kwargs['audio_kwargs']['sampling_rate']}, which can result in silent errors."
|
| 109 |
+
)
|
| 110 |
+
elif sampling_rate != output_kwargs["audio_kwargs"]["sampling_rate"]:
|
| 111 |
+
raise ValueError(
|
| 112 |
+
f"The sampling rate of the audio ({sampling_rate}) does not match the sampling rate of the processor ({output_kwargs['audio_kwargs']['sampling_rate']}). Please provide resampled the audio to the expected sampling rate."
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
inputs = self.feature_extractor(audio, **output_kwargs["audio_kwargs"])
|
| 116 |
+
|
| 117 |
+
prompt_ids = self.get_decoder_prompt_ids(language=language, punctuation=punctuation)
|
| 118 |
+
batch_size = inputs["input_features"].shape[0]
|
| 119 |
+
inputs["decoder_input_ids"] = torch.tensor([prompt_ids] * batch_size, dtype=torch.long)
|
| 120 |
+
|
| 121 |
+
if text is not None:
|
| 122 |
+
encodings = self.tokenizer(text, **output_kwargs["text_kwargs"])
|
| 123 |
+
inputs["labels"] = encodings["input_ids"]
|
| 124 |
+
|
| 125 |
+
return inputs
|
| 126 |
+
|
| 127 |
+
def decode(self, *args, audio_chunk_index=None, language=None, **kwargs):
|
| 128 |
+
texts = self.tokenizer.decode(*args, **kwargs)
|
| 129 |
+
if audio_chunk_index is None:
|
| 130 |
+
return texts
|
| 131 |
+
if language is None:
|
| 132 |
+
raise ValueError("`language` must be provided when `audio_chunk_index` is given.")
|
| 133 |
+
separator = "" if language in _NO_SPACE_LANGS else " "
|
| 134 |
+
return self._reassemble_chunk_texts(texts, audio_chunk_index, separator)
|
| 135 |
+
|
| 136 |
+
@staticmethod
|
| 137 |
+
def _reassemble_chunk_texts(
|
| 138 |
+
texts: list[str],
|
| 139 |
+
audio_chunk_index: list[tuple[int, int | None]],
|
| 140 |
+
separator: str = " ",
|
| 141 |
+
) -> list[str]:
|
| 142 |
+
"""Reassemble per-chunk transcription texts back into per-sample strings.
|
| 143 |
+
|
| 144 |
+
When audio inputs are longer than the feature extractor's `max_audio_clip_s`, they are split into
|
| 145 |
+
overlapping chunks before being fed to the model. This means a single original audio sample can
|
| 146 |
+
produce multiple decoded text segments. This method reverses that chunking: it groups the decoded
|
| 147 |
+
texts by their original sample index using `chunk_map`, orders the chunks, and joins them
|
| 148 |
+
with `separator` to reconstruct one transcription string per input sample.
|
| 149 |
+
|
| 150 |
+
Args:
|
| 151 |
+
texts: Decoded text strings, one per model output (i.e. one per chunk).
|
| 152 |
+
audio_chunk_index: List of `(sample_idx, chunk_idx)` tuples that map each entry in
|
| 153 |
+
`texts` back to its original sample and chunk position. A `chunk_idx` of `None`
|
| 154 |
+
indicates the sample was not chunked.
|
| 155 |
+
separator: String used to join chunks belonging to the same sample. Defaults to a
|
| 156 |
+
space; callers pass an empty string for languages that don't use spaces between
|
| 157 |
+
words (e.g. Chinese, Japanese).
|
| 158 |
+
|
| 159 |
+
Returns:
|
| 160 |
+
A list of reassembled transcription strings, one per original input sample.
|
| 161 |
+
"""
|
| 162 |
+
max_sample_idx = max(sample_idx for sample_idx, _ in audio_chunk_index)
|
| 163 |
+
outputs = [""] * (max_sample_idx + 1)
|
| 164 |
+
chunked = {}
|
| 165 |
+
|
| 166 |
+
for (sample_idx, chunk_idx), text in zip(audio_chunk_index, texts):
|
| 167 |
+
if chunk_idx is None:
|
| 168 |
+
outputs[sample_idx] = text
|
| 169 |
+
else:
|
| 170 |
+
if sample_idx not in chunked:
|
| 171 |
+
chunked[sample_idx] = []
|
| 172 |
+
chunked[sample_idx].append((chunk_idx, text))
|
| 173 |
+
|
| 174 |
+
for sample_idx, chunk_items in chunked.items():
|
| 175 |
+
chunk_items.sort(key=lambda item: item[0])
|
| 176 |
+
non_empty = [t for _, t in chunk_items if t and t.strip()]
|
| 177 |
+
parts = [non_empty[0].rstrip()] + [t.strip() for t in non_empty[1:]]
|
| 178 |
+
outputs[sample_idx] = separator.join(parts)
|
| 179 |
+
|
| 180 |
+
return outputs
|
| 181 |
+
|
| 182 |
+
@property
|
| 183 |
+
def model_input_names(self):
|
| 184 |
+
feature_extractor_input_names = self.feature_extractor.model_input_names
|
| 185 |
+
return feature_extractor_input_names + ["labels"]
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
__all__ = ["CohereAsrProcessor"]
|
LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/esm/openfold_utils/chunk_utils.py
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2021 AlQuraishi Laboratory
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
import logging
|
| 15 |
+
import math
|
| 16 |
+
from collections.abc import Callable, Iterable, Sequence
|
| 17 |
+
from functools import partial
|
| 18 |
+
from typing import Any
|
| 19 |
+
|
| 20 |
+
import torch
|
| 21 |
+
|
| 22 |
+
from .tensor_utils import tensor_tree_map, tree_map
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _fetch_dims(tree: dict | list | tuple | torch.Tensor) -> list[tuple[int, ...]]:
|
| 26 |
+
shapes = []
|
| 27 |
+
if isinstance(tree, dict):
|
| 28 |
+
for v in tree.values():
|
| 29 |
+
shapes.extend(_fetch_dims(v))
|
| 30 |
+
elif isinstance(tree, (list, tuple)):
|
| 31 |
+
for t in tree:
|
| 32 |
+
shapes.extend(_fetch_dims(t))
|
| 33 |
+
elif isinstance(tree, torch.Tensor):
|
| 34 |
+
shapes.append(tree.shape)
|
| 35 |
+
else:
|
| 36 |
+
raise TypeError("Not supported")
|
| 37 |
+
|
| 38 |
+
return shapes
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
@torch.jit.ignore
|
| 42 |
+
def _flat_idx_to_idx(flat_idx: int, dims: tuple[int, ...]) -> tuple[int, ...]:
|
| 43 |
+
idx = []
|
| 44 |
+
for d in reversed(dims):
|
| 45 |
+
idx.append(flat_idx % d)
|
| 46 |
+
flat_idx = flat_idx // d
|
| 47 |
+
|
| 48 |
+
return tuple(reversed(idx))
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
@torch.jit.ignore
|
| 52 |
+
def _get_minimal_slice_set(
|
| 53 |
+
start: Sequence[int],
|
| 54 |
+
end: Sequence[int],
|
| 55 |
+
dims: Sequence[int],
|
| 56 |
+
start_edges: Sequence[bool] | None = None,
|
| 57 |
+
end_edges: Sequence[bool] | None = None,
|
| 58 |
+
) -> list[tuple[slice, ...]]:
|
| 59 |
+
"""
|
| 60 |
+
Produces an ordered sequence of tensor slices that, when used in sequence on a tensor with shape dims, yields
|
| 61 |
+
tensors that contain every leaf in the contiguous range [start, end]. Care is taken to yield a short sequence of
|
| 62 |
+
slices, and perhaps even the shortest possible (I'm pretty sure it's the latter).
|
| 63 |
+
|
| 64 |
+
end is INCLUSIVE.
|
| 65 |
+
"""
|
| 66 |
+
|
| 67 |
+
# start_edges and end_edges both indicate whether, starting from any given
|
| 68 |
+
# dimension, the start/end index is at the top/bottom edge of the
|
| 69 |
+
# corresponding tensor, modeled as a tree
|
| 70 |
+
def reduce_edge_list(l: list[bool]) -> None:
|
| 71 |
+
tally = True
|
| 72 |
+
for i in range(len(l)):
|
| 73 |
+
reversed_idx = -1 * (i + 1)
|
| 74 |
+
l[reversed_idx] &= tally
|
| 75 |
+
tally = l[reversed_idx]
|
| 76 |
+
|
| 77 |
+
if start_edges is None:
|
| 78 |
+
start_edges = [s == 0 for s in start]
|
| 79 |
+
reduce_edge_list(start_edges)
|
| 80 |
+
if end_edges is None:
|
| 81 |
+
end_edges = [e == (d - 1) for e, d in zip(end, dims)]
|
| 82 |
+
reduce_edge_list(end_edges)
|
| 83 |
+
|
| 84 |
+
# Base cases. Either start/end are empty and we're done, or the final,
|
| 85 |
+
# one-dimensional tensor can be simply sliced
|
| 86 |
+
if len(start) == 0:
|
| 87 |
+
return [()]
|
| 88 |
+
elif len(start) == 1:
|
| 89 |
+
return [(slice(start[0], end[0] + 1),)]
|
| 90 |
+
|
| 91 |
+
slices: list[tuple[slice, ...]] = []
|
| 92 |
+
path_list: list[slice] = []
|
| 93 |
+
|
| 94 |
+
# Dimensions common to start and end can be selected directly
|
| 95 |
+
for s, e in zip(start, end):
|
| 96 |
+
if s == e:
|
| 97 |
+
path_list.append(slice(s, s + 1))
|
| 98 |
+
else:
|
| 99 |
+
break
|
| 100 |
+
|
| 101 |
+
path: tuple[slice, ...] = tuple(path_list)
|
| 102 |
+
divergence_idx = len(path)
|
| 103 |
+
|
| 104 |
+
# start == end, and we're done
|
| 105 |
+
if divergence_idx == len(dims):
|
| 106 |
+
return [path]
|
| 107 |
+
|
| 108 |
+
def upper() -> tuple[tuple[slice, ...], ...]:
|
| 109 |
+
assert start_edges is not None
|
| 110 |
+
assert end_edges is not None
|
| 111 |
+
|
| 112 |
+
sdi = start[divergence_idx]
|
| 113 |
+
return tuple(
|
| 114 |
+
path + (slice(sdi, sdi + 1),) + s
|
| 115 |
+
for s in _get_minimal_slice_set(
|
| 116 |
+
start[divergence_idx + 1 :],
|
| 117 |
+
[d - 1 for d in dims[divergence_idx + 1 :]],
|
| 118 |
+
dims[divergence_idx + 1 :],
|
| 119 |
+
start_edges=start_edges[divergence_idx + 1 :],
|
| 120 |
+
end_edges=[True for _ in end_edges[divergence_idx + 1 :]],
|
| 121 |
+
)
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
def lower() -> tuple[tuple[slice, ...], ...]:
|
| 125 |
+
assert start_edges is not None
|
| 126 |
+
assert end_edges is not None
|
| 127 |
+
|
| 128 |
+
edi = end[divergence_idx]
|
| 129 |
+
return tuple(
|
| 130 |
+
path + (slice(edi, edi + 1),) + s
|
| 131 |
+
for s in _get_minimal_slice_set(
|
| 132 |
+
[0 for _ in start[divergence_idx + 1 :]],
|
| 133 |
+
end[divergence_idx + 1 :],
|
| 134 |
+
dims[divergence_idx + 1 :],
|
| 135 |
+
start_edges=[True for _ in start_edges[divergence_idx + 1 :]],
|
| 136 |
+
end_edges=end_edges[divergence_idx + 1 :],
|
| 137 |
+
)
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
# If both start and end are at the edges of the subtree rooted at
|
| 141 |
+
# divergence_idx, we can just select the whole subtree at once
|
| 142 |
+
if start_edges[divergence_idx] and end_edges[divergence_idx]:
|
| 143 |
+
slices.append(path + (slice(start[divergence_idx], end[divergence_idx] + 1),))
|
| 144 |
+
# If just start is at the edge, we can grab almost all of the subtree,
|
| 145 |
+
# treating only the ragged bottom edge as an edge case
|
| 146 |
+
elif start_edges[divergence_idx]:
|
| 147 |
+
slices.append(path + (slice(start[divergence_idx], end[divergence_idx]),))
|
| 148 |
+
slices.extend(lower())
|
| 149 |
+
# Analogous to the previous case, but the top is ragged this time
|
| 150 |
+
elif end_edges[divergence_idx]:
|
| 151 |
+
slices.extend(upper())
|
| 152 |
+
slices.append(path + (slice(start[divergence_idx] + 1, end[divergence_idx] + 1),))
|
| 153 |
+
# If both sides of the range are ragged, we need to handle both sides
|
| 154 |
+
# separately. If there's contiguous meat in between them, we can index it
|
| 155 |
+
# in one big chunk
|
| 156 |
+
else:
|
| 157 |
+
slices.extend(upper())
|
| 158 |
+
middle_ground = end[divergence_idx] - start[divergence_idx]
|
| 159 |
+
if middle_ground > 1:
|
| 160 |
+
slices.append(path + (slice(start[divergence_idx] + 1, end[divergence_idx]),))
|
| 161 |
+
slices.extend(lower())
|
| 162 |
+
|
| 163 |
+
return slices
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
@torch.jit.ignore
|
| 167 |
+
def _chunk_slice(t: torch.Tensor, flat_start: int, flat_end: int, no_batch_dims: int) -> torch.Tensor:
|
| 168 |
+
"""
|
| 169 |
+
Equivalent to
|
| 170 |
+
|
| 171 |
+
t.reshape((-1,) + t.shape[no_batch_dims:])[flat_start:flat_end]
|
| 172 |
+
|
| 173 |
+
but without the need for the initial reshape call, which can be memory-intensive in certain situations. The only
|
| 174 |
+
reshape operations in this function are performed on sub-tensors that scale with (flat_end - flat_start), the chunk
|
| 175 |
+
size.
|
| 176 |
+
"""
|
| 177 |
+
|
| 178 |
+
batch_dims = t.shape[:no_batch_dims]
|
| 179 |
+
start_idx = list(_flat_idx_to_idx(flat_start, batch_dims))
|
| 180 |
+
# _get_minimal_slice_set is inclusive
|
| 181 |
+
end_idx = list(_flat_idx_to_idx(flat_end - 1, batch_dims))
|
| 182 |
+
|
| 183 |
+
# Get an ordered list of slices to perform
|
| 184 |
+
slices = _get_minimal_slice_set(
|
| 185 |
+
start_idx,
|
| 186 |
+
end_idx,
|
| 187 |
+
batch_dims,
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
sliced_tensors = [t[s] for s in slices]
|
| 191 |
+
|
| 192 |
+
return torch.cat([s.view((-1,) + t.shape[no_batch_dims:]) for s in sliced_tensors])
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
def chunk_layer(
|
| 196 |
+
layer: Callable,
|
| 197 |
+
inputs: dict[str, Any],
|
| 198 |
+
chunk_size: int,
|
| 199 |
+
no_batch_dims: int,
|
| 200 |
+
low_mem: bool = False,
|
| 201 |
+
_out: Any = None,
|
| 202 |
+
_add_into_out: bool = False,
|
| 203 |
+
) -> Any:
|
| 204 |
+
"""
|
| 205 |
+
Implements the "chunking" procedure described in section 1.11.8.
|
| 206 |
+
|
| 207 |
+
Layer outputs and inputs are assumed to be simple "pytrees," consisting only of (arbitrarily nested) lists, tuples,
|
| 208 |
+
and dicts with torch.Tensor leaves.
|
| 209 |
+
|
| 210 |
+
Args:
|
| 211 |
+
layer:
|
| 212 |
+
The layer to be applied chunk-wise
|
| 213 |
+
inputs:
|
| 214 |
+
A (non-nested) dictionary of keyworded inputs. All leaves must be tensors and must share the same batch
|
| 215 |
+
dimensions.
|
| 216 |
+
chunk_size:
|
| 217 |
+
The number of sub-batches per chunk. If multiple batch dimensions are specified, a "sub-batch" is defined
|
| 218 |
+
as a single indexing of all batch dimensions simultaneously (s.t. the number of sub-batches is the product
|
| 219 |
+
of the batch dimensions).
|
| 220 |
+
no_batch_dims:
|
| 221 |
+
How many of the initial dimensions of each input tensor can be considered batch dimensions.
|
| 222 |
+
low_mem:
|
| 223 |
+
Avoids flattening potentially large input tensors. Unnecessary in most cases, and is ever so slightly
|
| 224 |
+
slower than the default setting.
|
| 225 |
+
Returns:
|
| 226 |
+
The reassembled output of the layer on the inputs.
|
| 227 |
+
"""
|
| 228 |
+
if not (len(inputs) > 0):
|
| 229 |
+
raise ValueError("Must provide at least one input")
|
| 230 |
+
|
| 231 |
+
initial_dims = [shape[:no_batch_dims] for shape in _fetch_dims(inputs)]
|
| 232 |
+
orig_batch_dims = tuple(max(s) for s in zip(*initial_dims))
|
| 233 |
+
|
| 234 |
+
def _prep_inputs(t: torch.Tensor) -> torch.Tensor:
|
| 235 |
+
if not low_mem:
|
| 236 |
+
if sum(t.shape[:no_batch_dims]) != no_batch_dims:
|
| 237 |
+
t = t.expand(orig_batch_dims + t.shape[no_batch_dims:])
|
| 238 |
+
t = t.reshape(-1, *t.shape[no_batch_dims:])
|
| 239 |
+
else:
|
| 240 |
+
t = t.expand(orig_batch_dims + t.shape[no_batch_dims:])
|
| 241 |
+
return t
|
| 242 |
+
|
| 243 |
+
prepped_inputs: dict[str, Any] = tensor_tree_map(_prep_inputs, inputs)
|
| 244 |
+
prepped_outputs = None
|
| 245 |
+
if _out is not None:
|
| 246 |
+
prepped_outputs = tensor_tree_map(lambda t: t.view([-1] + list(t.shape[no_batch_dims:])), _out)
|
| 247 |
+
|
| 248 |
+
flat_batch_dim = 1
|
| 249 |
+
for d in orig_batch_dims:
|
| 250 |
+
flat_batch_dim *= d
|
| 251 |
+
|
| 252 |
+
no_chunks = flat_batch_dim // chunk_size + (flat_batch_dim % chunk_size != 0)
|
| 253 |
+
|
| 254 |
+
def _select_chunk(t: torch.Tensor) -> torch.Tensor:
|
| 255 |
+
return t[i : i + chunk_size] if t.shape[0] != 1 else t
|
| 256 |
+
|
| 257 |
+
i = 0
|
| 258 |
+
out = prepped_outputs
|
| 259 |
+
for _ in range(no_chunks):
|
| 260 |
+
# Chunk the input
|
| 261 |
+
if not low_mem:
|
| 262 |
+
select_chunk = _select_chunk
|
| 263 |
+
else:
|
| 264 |
+
select_chunk = partial(
|
| 265 |
+
_chunk_slice,
|
| 266 |
+
flat_start=i,
|
| 267 |
+
flat_end=min(flat_batch_dim, i + chunk_size),
|
| 268 |
+
no_batch_dims=len(orig_batch_dims),
|
| 269 |
+
)
|
| 270 |
+
|
| 271 |
+
chunks: dict[str, Any] = tensor_tree_map(select_chunk, prepped_inputs)
|
| 272 |
+
|
| 273 |
+
# Run the layer on the chunk
|
| 274 |
+
output_chunk = layer(**chunks)
|
| 275 |
+
|
| 276 |
+
# Allocate space for the output
|
| 277 |
+
if out is None:
|
| 278 |
+
out = tensor_tree_map(lambda t: t.new_zeros((flat_batch_dim,) + t.shape[1:]), output_chunk)
|
| 279 |
+
|
| 280 |
+
# Put the chunk in its pre-allocated space
|
| 281 |
+
if isinstance(output_chunk, dict):
|
| 282 |
+
|
| 283 |
+
def assign(d1: dict, d2: dict) -> None:
|
| 284 |
+
for k, v in d1.items():
|
| 285 |
+
if isinstance(v, dict):
|
| 286 |
+
assign(v, d2[k])
|
| 287 |
+
else:
|
| 288 |
+
if _add_into_out:
|
| 289 |
+
v[i : i + chunk_size] += d2[k]
|
| 290 |
+
else:
|
| 291 |
+
v[i : i + chunk_size] = d2[k]
|
| 292 |
+
|
| 293 |
+
assign(out, output_chunk)
|
| 294 |
+
elif isinstance(output_chunk, tuple):
|
| 295 |
+
for x1, x2 in zip(out, output_chunk):
|
| 296 |
+
if _add_into_out:
|
| 297 |
+
x1[i : i + chunk_size] += x2
|
| 298 |
+
else:
|
| 299 |
+
x1[i : i + chunk_size] = x2
|
| 300 |
+
elif isinstance(output_chunk, torch.Tensor):
|
| 301 |
+
if _add_into_out:
|
| 302 |
+
out[i : i + chunk_size] += output_chunk
|
| 303 |
+
else:
|
| 304 |
+
out[i : i + chunk_size] = output_chunk
|
| 305 |
+
else:
|
| 306 |
+
raise TypeError("Not supported")
|
| 307 |
+
|
| 308 |
+
i += chunk_size
|
| 309 |
+
|
| 310 |
+
out = tensor_tree_map(lambda t: t.view(orig_batch_dims + t.shape[1:]), out)
|
| 311 |
+
|
| 312 |
+
return out
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
class ChunkSizeTuner:
|
| 316 |
+
def __init__(
|
| 317 |
+
self,
|
| 318 |
+
# Heuristically, runtimes for most of the modules in the network
|
| 319 |
+
# plateau earlier than this on all GPUs I've run the model on.
|
| 320 |
+
max_chunk_size: int = 512,
|
| 321 |
+
):
|
| 322 |
+
self.max_chunk_size = max_chunk_size
|
| 323 |
+
self.cached_chunk_size: int | None = None
|
| 324 |
+
self.cached_arg_data: tuple | None = None
|
| 325 |
+
|
| 326 |
+
def _determine_favorable_chunk_size(self, fn: Callable, args: tuple, min_chunk_size: int) -> int:
|
| 327 |
+
logging.info("Tuning chunk size...")
|
| 328 |
+
|
| 329 |
+
if min_chunk_size >= self.max_chunk_size:
|
| 330 |
+
return min_chunk_size
|
| 331 |
+
|
| 332 |
+
candidates: list[int] = [2**l for l in range(int(math.log2(self.max_chunk_size)) + 1)]
|
| 333 |
+
candidates = [c for c in candidates if c > min_chunk_size]
|
| 334 |
+
candidates = [min_chunk_size] + candidates
|
| 335 |
+
candidates[-1] += 4
|
| 336 |
+
|
| 337 |
+
def test_chunk_size(chunk_size: int) -> bool:
|
| 338 |
+
try:
|
| 339 |
+
with torch.no_grad():
|
| 340 |
+
fn(*args, chunk_size=chunk_size)
|
| 341 |
+
return True
|
| 342 |
+
except RuntimeError:
|
| 343 |
+
return False
|
| 344 |
+
|
| 345 |
+
min_viable_chunk_size_index = 0
|
| 346 |
+
i = len(candidates) - 1
|
| 347 |
+
while i > min_viable_chunk_size_index:
|
| 348 |
+
viable = test_chunk_size(candidates[i])
|
| 349 |
+
if not viable:
|
| 350 |
+
i = (min_viable_chunk_size_index + i) // 2
|
| 351 |
+
else:
|
| 352 |
+
min_viable_chunk_size_index = i
|
| 353 |
+
i = (i + len(candidates) - 1) // 2
|
| 354 |
+
|
| 355 |
+
return candidates[min_viable_chunk_size_index]
|
| 356 |
+
|
| 357 |
+
def _compare_arg_caches(self, ac1: Iterable, ac2: Iterable) -> bool:
|
| 358 |
+
consistent = True
|
| 359 |
+
for a1, a2 in zip(ac1, ac2):
|
| 360 |
+
assert type(ac1) is type(ac2)
|
| 361 |
+
if isinstance(ac1, (list, tuple)):
|
| 362 |
+
consistent &= self._compare_arg_caches(a1, a2)
|
| 363 |
+
elif isinstance(ac1, dict):
|
| 364 |
+
a1_items = [v for _, v in sorted(a1.items(), key=lambda x: x[0])]
|
| 365 |
+
a2_items = [v for _, v in sorted(a2.items(), key=lambda x: x[0])]
|
| 366 |
+
consistent &= self._compare_arg_caches(a1_items, a2_items)
|
| 367 |
+
else:
|
| 368 |
+
consistent &= a1 == a2
|
| 369 |
+
|
| 370 |
+
return consistent
|
| 371 |
+
|
| 372 |
+
def tune_chunk_size(
|
| 373 |
+
self,
|
| 374 |
+
representative_fn: Callable,
|
| 375 |
+
args: tuple,
|
| 376 |
+
min_chunk_size: int,
|
| 377 |
+
) -> int:
|
| 378 |
+
consistent = True
|
| 379 |
+
arg_data: tuple = tree_map(lambda a: a.shape if isinstance(a, torch.Tensor) else a, args, object)
|
| 380 |
+
if self.cached_arg_data is not None:
|
| 381 |
+
# If args have changed shape/value, we need to re-tune
|
| 382 |
+
assert len(self.cached_arg_data) == len(arg_data)
|
| 383 |
+
consistent = self._compare_arg_caches(self.cached_arg_data, arg_data)
|
| 384 |
+
else:
|
| 385 |
+
# Otherwise, we can reuse the precomputed value
|
| 386 |
+
consistent = False
|
| 387 |
+
|
| 388 |
+
if not consistent:
|
| 389 |
+
self.cached_chunk_size = self._determine_favorable_chunk_size(
|
| 390 |
+
representative_fn,
|
| 391 |
+
args,
|
| 392 |
+
min_chunk_size,
|
| 393 |
+
)
|
| 394 |
+
self.cached_arg_data = arg_data
|
| 395 |
+
|
| 396 |
+
assert self.cached_chunk_size is not None
|
| 397 |
+
|
| 398 |
+
return self.cached_chunk_size
|
LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/esm/openfold_utils/loss.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2021 AlQuraishi Laboratory
|
| 2 |
+
# Copyright 2021 DeepMind Technologies Limited
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _calculate_bin_centers(boundaries: torch.Tensor) -> torch.Tensor:
|
| 21 |
+
step = boundaries[1] - boundaries[0]
|
| 22 |
+
bin_centers = boundaries + step / 2
|
| 23 |
+
bin_centers = torch.cat([bin_centers, (bin_centers[-1] + step).unsqueeze(-1)], dim=0)
|
| 24 |
+
return bin_centers
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def _calculate_expected_aligned_error(
|
| 28 |
+
alignment_confidence_breaks: torch.Tensor,
|
| 29 |
+
aligned_distance_error_probs: torch.Tensor,
|
| 30 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 31 |
+
bin_centers = _calculate_bin_centers(alignment_confidence_breaks)
|
| 32 |
+
return (
|
| 33 |
+
torch.sum(aligned_distance_error_probs * bin_centers, dim=-1),
|
| 34 |
+
bin_centers[-1],
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def compute_predicted_aligned_error(
|
| 39 |
+
logits: torch.Tensor,
|
| 40 |
+
max_bin: int = 31,
|
| 41 |
+
no_bins: int = 64,
|
| 42 |
+
**kwargs,
|
| 43 |
+
) -> dict[str, torch.Tensor]:
|
| 44 |
+
"""Computes aligned confidence metrics from logits.
|
| 45 |
+
|
| 46 |
+
Args:
|
| 47 |
+
logits: [*, num_res, num_res, num_bins] the logits output from
|
| 48 |
+
PredictedAlignedErrorHead.
|
| 49 |
+
max_bin: Maximum bin value
|
| 50 |
+
no_bins: Number of bins
|
| 51 |
+
Returns:
|
| 52 |
+
aligned_confidence_probs: [*, num_res, num_res, num_bins] the predicted
|
| 53 |
+
aligned error probabilities over bins for each residue pair.
|
| 54 |
+
predicted_aligned_error: [*, num_res, num_res] the expected aligned distance
|
| 55 |
+
error for each pair of residues.
|
| 56 |
+
max_predicted_aligned_error: [*] the maximum predicted error possible.
|
| 57 |
+
"""
|
| 58 |
+
boundaries = torch.linspace(0, max_bin, steps=(no_bins - 1), device=logits.device)
|
| 59 |
+
|
| 60 |
+
aligned_confidence_probs = torch.nn.functional.softmax(logits, dim=-1)
|
| 61 |
+
predicted_aligned_error, max_predicted_aligned_error = _calculate_expected_aligned_error(
|
| 62 |
+
alignment_confidence_breaks=boundaries,
|
| 63 |
+
aligned_distance_error_probs=aligned_confidence_probs,
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
return {
|
| 67 |
+
"aligned_confidence_probs": aligned_confidence_probs,
|
| 68 |
+
"predicted_aligned_error": predicted_aligned_error,
|
| 69 |
+
"max_predicted_aligned_error": max_predicted_aligned_error,
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def compute_tm(
|
| 74 |
+
logits: torch.Tensor,
|
| 75 |
+
residue_weights: torch.Tensor | None = None,
|
| 76 |
+
max_bin: int = 31,
|
| 77 |
+
no_bins: int = 64,
|
| 78 |
+
eps: float = 1e-8,
|
| 79 |
+
**kwargs,
|
| 80 |
+
) -> torch.Tensor:
|
| 81 |
+
if residue_weights is None:
|
| 82 |
+
residue_weights = logits.new_ones(logits.shape[-2])
|
| 83 |
+
|
| 84 |
+
boundaries = torch.linspace(0, max_bin, steps=(no_bins - 1), device=logits.device)
|
| 85 |
+
|
| 86 |
+
bin_centers = _calculate_bin_centers(boundaries)
|
| 87 |
+
torch.sum(residue_weights)
|
| 88 |
+
n = logits.shape[-2]
|
| 89 |
+
clipped_n = max(n, 19)
|
| 90 |
+
|
| 91 |
+
d0 = 1.24 * (clipped_n - 15) ** (1.0 / 3) - 1.8
|
| 92 |
+
|
| 93 |
+
probs = torch.nn.functional.softmax(logits, dim=-1)
|
| 94 |
+
|
| 95 |
+
tm_per_bin = 1.0 / (1 + (bin_centers**2) / (d0**2))
|
| 96 |
+
predicted_tm_term = torch.sum(probs * tm_per_bin, dim=-1)
|
| 97 |
+
|
| 98 |
+
normed_residue_mask = residue_weights / (eps + residue_weights.sum())
|
| 99 |
+
per_alignment = torch.sum(predicted_tm_term * normed_residue_mask, dim=-1)
|
| 100 |
+
|
| 101 |
+
weighted = per_alignment * residue_weights
|
| 102 |
+
|
| 103 |
+
argmax = (weighted == torch.max(weighted)).nonzero()[0]
|
| 104 |
+
return per_alignment[tuple(argmax)]
|
LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/esm/openfold_utils/protein.py
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2021 AlQuraishi Laboratory
|
| 2 |
+
# Copyright 2021 DeepMind Technologies Limited
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
"""Protein data type."""
|
| 17 |
+
|
| 18 |
+
import dataclasses
|
| 19 |
+
import re
|
| 20 |
+
import string
|
| 21 |
+
from collections.abc import Iterator, Mapping, Sequence
|
| 22 |
+
from typing import Any
|
| 23 |
+
|
| 24 |
+
import numpy as np
|
| 25 |
+
|
| 26 |
+
from . import residue_constants
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
FeatureDict = Mapping[str, np.ndarray]
|
| 30 |
+
ModelOutput = Mapping[str, Any] # Is a nested dict.
|
| 31 |
+
PICO_TO_ANGSTROM = 0.01
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
@dataclasses.dataclass(frozen=True)
|
| 35 |
+
class Protein:
|
| 36 |
+
"""Protein structure representation."""
|
| 37 |
+
|
| 38 |
+
# Cartesian coordinates of atoms in angstroms. The atom types correspond to
|
| 39 |
+
# residue_constants.atom_types, i.e. the first three are N, CA, CB.
|
| 40 |
+
atom_positions: np.ndarray # [num_res, num_atom_type, 3]
|
| 41 |
+
|
| 42 |
+
# Amino-acid type for each residue represented as an integer between 0 and
|
| 43 |
+
# 20, where 20 is 'X'.
|
| 44 |
+
aatype: np.ndarray # [num_res]
|
| 45 |
+
|
| 46 |
+
# Binary float mask to indicate presence of a particular atom. 1.0 if an atom
|
| 47 |
+
# is present and 0.0 if not. This should be used for loss masking.
|
| 48 |
+
atom_mask: np.ndarray # [num_res, num_atom_type]
|
| 49 |
+
|
| 50 |
+
# Residue index as used in PDB. It is not necessarily continuous or 0-indexed.
|
| 51 |
+
residue_index: np.ndarray # [num_res]
|
| 52 |
+
|
| 53 |
+
# B-factors, or temperature factors, of each residue (in sq. angstroms units),
|
| 54 |
+
# representing the displacement of the residue from its ground truth mean
|
| 55 |
+
# value.
|
| 56 |
+
b_factors: np.ndarray # [num_res, num_atom_type]
|
| 57 |
+
|
| 58 |
+
# Chain indices for multi-chain predictions
|
| 59 |
+
chain_index: np.ndarray | None = None
|
| 60 |
+
|
| 61 |
+
# Optional remark about the protein. Included as a comment in output PDB
|
| 62 |
+
# files
|
| 63 |
+
remark: str | None = None
|
| 64 |
+
|
| 65 |
+
# Templates used to generate this protein (prediction-only)
|
| 66 |
+
parents: Sequence[str] | None = None
|
| 67 |
+
|
| 68 |
+
# Chain corresponding to each parent
|
| 69 |
+
parents_chain_index: Sequence[int] | None = None
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def from_proteinnet_string(proteinnet_str: str) -> Protein:
|
| 73 |
+
tag_re = r"(\[[A-Z]+\]\n)"
|
| 74 |
+
tags: list[str] = [tag.strip() for tag in re.split(tag_re, proteinnet_str) if len(tag) > 0]
|
| 75 |
+
groups: Iterator[tuple[str, list[str]]] = zip(tags[0::2], [l.split("\n") for l in tags[1::2]])
|
| 76 |
+
|
| 77 |
+
atoms: list[str] = ["N", "CA", "C"]
|
| 78 |
+
aatype = None
|
| 79 |
+
atom_positions = None
|
| 80 |
+
atom_mask = None
|
| 81 |
+
for g in groups:
|
| 82 |
+
if g[0] == "[PRIMARY]":
|
| 83 |
+
seq = g[1][0].strip()
|
| 84 |
+
# Replace unknown residues with "X" (strings are immutable, so convert to list first)
|
| 85 |
+
seq = [char if char in residue_constants.restypes else "X" for char in seq]
|
| 86 |
+
aatype = np.array(
|
| 87 |
+
[residue_constants.restype_order.get(res_symbol, residue_constants.restype_num) for res_symbol in seq]
|
| 88 |
+
)
|
| 89 |
+
elif g[0] == "[TERTIARY]":
|
| 90 |
+
tertiary: list[list[float]] = []
|
| 91 |
+
for axis in range(3):
|
| 92 |
+
tertiary.append(list(map(float, g[1][axis].split())))
|
| 93 |
+
tertiary_np = np.array(tertiary)
|
| 94 |
+
atom_positions = np.zeros((len(tertiary[0]) // 3, residue_constants.atom_type_num, 3)).astype(np.float32)
|
| 95 |
+
for i, atom in enumerate(atoms):
|
| 96 |
+
atom_positions[:, residue_constants.atom_order[atom], :] = np.transpose(tertiary_np[:, i::3])
|
| 97 |
+
atom_positions *= PICO_TO_ANGSTROM
|
| 98 |
+
elif g[0] == "[MASK]":
|
| 99 |
+
mask = np.array(list(map({"-": 0, "+": 1}.get, g[1][0].strip())))
|
| 100 |
+
atom_mask = np.zeros(
|
| 101 |
+
(
|
| 102 |
+
len(mask),
|
| 103 |
+
residue_constants.atom_type_num,
|
| 104 |
+
)
|
| 105 |
+
).astype(np.float32)
|
| 106 |
+
for i, atom in enumerate(atoms):
|
| 107 |
+
atom_mask[:, residue_constants.atom_order[atom]] = 1
|
| 108 |
+
atom_mask *= mask[..., None]
|
| 109 |
+
|
| 110 |
+
assert aatype is not None
|
| 111 |
+
|
| 112 |
+
return Protein(
|
| 113 |
+
atom_positions=atom_positions,
|
| 114 |
+
atom_mask=atom_mask,
|
| 115 |
+
aatype=aatype,
|
| 116 |
+
residue_index=np.arange(len(aatype)),
|
| 117 |
+
b_factors=None,
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def get_pdb_headers(prot: Protein, chain_id: int = 0) -> list[str]:
|
| 122 |
+
pdb_headers: list[str] = []
|
| 123 |
+
|
| 124 |
+
remark = prot.remark
|
| 125 |
+
if remark is not None:
|
| 126 |
+
pdb_headers.append(f"REMARK {remark}")
|
| 127 |
+
|
| 128 |
+
parents = prot.parents
|
| 129 |
+
parents_chain_index = prot.parents_chain_index
|
| 130 |
+
if parents is not None and parents_chain_index is not None:
|
| 131 |
+
parents = [p for i, p in zip(parents_chain_index, parents) if i == chain_id]
|
| 132 |
+
|
| 133 |
+
if parents is None or len(parents) == 0:
|
| 134 |
+
parents = ["N/A"]
|
| 135 |
+
|
| 136 |
+
pdb_headers.append(f"PARENT {' '.join(parents)}")
|
| 137 |
+
|
| 138 |
+
return pdb_headers
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def add_pdb_headers(prot: Protein, pdb_str: str) -> str:
|
| 142 |
+
"""Add pdb headers to an existing PDB string. Useful during multi-chain
|
| 143 |
+
recycling
|
| 144 |
+
"""
|
| 145 |
+
out_pdb_lines: list[str] = []
|
| 146 |
+
lines = pdb_str.split("\n")
|
| 147 |
+
|
| 148 |
+
remark = prot.remark
|
| 149 |
+
if remark is not None:
|
| 150 |
+
out_pdb_lines.append(f"REMARK {remark}")
|
| 151 |
+
|
| 152 |
+
parents_per_chain: list[list[str]]
|
| 153 |
+
if prot.parents is not None and len(prot.parents) > 0:
|
| 154 |
+
parents_per_chain = []
|
| 155 |
+
if prot.parents_chain_index is not None:
|
| 156 |
+
parent_dict: dict[str, list[str]] = {}
|
| 157 |
+
for p, i in zip(prot.parents, prot.parents_chain_index):
|
| 158 |
+
parent_dict.setdefault(str(i), [])
|
| 159 |
+
parent_dict[str(i)].append(p)
|
| 160 |
+
|
| 161 |
+
max_idx = max(int(chain_idx) for chain_idx in parent_dict)
|
| 162 |
+
for i in range(max_idx + 1):
|
| 163 |
+
chain_parents = parent_dict.get(str(i), ["N/A"])
|
| 164 |
+
parents_per_chain.append(chain_parents)
|
| 165 |
+
else:
|
| 166 |
+
parents_per_chain.append(list(prot.parents))
|
| 167 |
+
else:
|
| 168 |
+
parents_per_chain = [["N/A"]]
|
| 169 |
+
|
| 170 |
+
def make_parent_line(p: Sequence[str]) -> str:
|
| 171 |
+
return f"PARENT {' '.join(p)}"
|
| 172 |
+
|
| 173 |
+
out_pdb_lines.append(make_parent_line(parents_per_chain[0]))
|
| 174 |
+
|
| 175 |
+
chain_counter = 0
|
| 176 |
+
for i, l in enumerate(lines):
|
| 177 |
+
if "PARENT" not in l and "REMARK" not in l:
|
| 178 |
+
out_pdb_lines.append(l)
|
| 179 |
+
if "TER" in l and "END" not in lines[i + 1]:
|
| 180 |
+
chain_counter += 1
|
| 181 |
+
if not chain_counter >= len(parents_per_chain):
|
| 182 |
+
chain_parents = parents_per_chain[chain_counter]
|
| 183 |
+
else:
|
| 184 |
+
chain_parents = ["N/A"]
|
| 185 |
+
|
| 186 |
+
out_pdb_lines.append(make_parent_line(chain_parents))
|
| 187 |
+
|
| 188 |
+
return "\n".join(out_pdb_lines)
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def to_pdb(prot: Protein) -> str:
|
| 192 |
+
"""Converts a `Protein` instance to a PDB string.
|
| 193 |
+
|
| 194 |
+
Args:
|
| 195 |
+
prot: The protein to convert to PDB.
|
| 196 |
+
|
| 197 |
+
Returns:
|
| 198 |
+
PDB string.
|
| 199 |
+
"""
|
| 200 |
+
restypes = residue_constants.restypes + ["X"]
|
| 201 |
+
|
| 202 |
+
def res_1to3(r: int) -> str:
|
| 203 |
+
return residue_constants.restype_1to3.get(restypes[r], "UNK")
|
| 204 |
+
|
| 205 |
+
atom_types = residue_constants.atom_types
|
| 206 |
+
|
| 207 |
+
pdb_lines: list[str] = []
|
| 208 |
+
|
| 209 |
+
atom_mask = prot.atom_mask
|
| 210 |
+
aatype = prot.aatype
|
| 211 |
+
atom_positions = prot.atom_positions
|
| 212 |
+
residue_index = prot.residue_index.astype(np.int32)
|
| 213 |
+
b_factors = prot.b_factors
|
| 214 |
+
chain_index = prot.chain_index
|
| 215 |
+
|
| 216 |
+
if np.any(aatype > residue_constants.restype_num):
|
| 217 |
+
raise ValueError("Invalid aatypes.")
|
| 218 |
+
|
| 219 |
+
headers = get_pdb_headers(prot)
|
| 220 |
+
if len(headers) > 0:
|
| 221 |
+
pdb_lines.extend(headers)
|
| 222 |
+
|
| 223 |
+
n = aatype.shape[0]
|
| 224 |
+
atom_index = 1
|
| 225 |
+
prev_chain_index = 0
|
| 226 |
+
chain_tags = string.ascii_uppercase
|
| 227 |
+
chain_tag = None
|
| 228 |
+
# Add all atom sites.
|
| 229 |
+
for i in range(n):
|
| 230 |
+
res_name_3 = res_1to3(aatype[i])
|
| 231 |
+
for atom_name, pos, mask, b_factor in zip(atom_types, atom_positions[i], atom_mask[i], b_factors[i]):
|
| 232 |
+
if mask < 0.5:
|
| 233 |
+
continue
|
| 234 |
+
|
| 235 |
+
record_type = "ATOM"
|
| 236 |
+
name = atom_name if len(atom_name) == 4 else f" {atom_name}"
|
| 237 |
+
alt_loc = ""
|
| 238 |
+
insertion_code = ""
|
| 239 |
+
occupancy = 1.00
|
| 240 |
+
element = atom_name[0] # Protein supports only C, N, O, S, this works.
|
| 241 |
+
charge = ""
|
| 242 |
+
|
| 243 |
+
chain_tag = "A"
|
| 244 |
+
if chain_index is not None:
|
| 245 |
+
chain_tag = chain_tags[chain_index[i]]
|
| 246 |
+
|
| 247 |
+
# PDB is a columnar format, every space matters here!
|
| 248 |
+
atom_line = (
|
| 249 |
+
f"{record_type:<6}{atom_index:>5} {name:<4}{alt_loc:>1}"
|
| 250 |
+
f"{res_name_3:>3} {chain_tag:>1}"
|
| 251 |
+
f"{residue_index[i]:>4}{insertion_code:>1} "
|
| 252 |
+
f"{pos[0]:>8.3f}{pos[1]:>8.3f}{pos[2]:>8.3f}"
|
| 253 |
+
f"{occupancy:>6.2f}{b_factor:>6.2f} "
|
| 254 |
+
f"{element:>2}{charge:>2}"
|
| 255 |
+
)
|
| 256 |
+
pdb_lines.append(atom_line)
|
| 257 |
+
atom_index += 1
|
| 258 |
+
|
| 259 |
+
should_terminate = i == n - 1
|
| 260 |
+
if chain_index is not None:
|
| 261 |
+
if i != n - 1 and chain_index[i + 1] != prev_chain_index:
|
| 262 |
+
should_terminate = True
|
| 263 |
+
prev_chain_index = chain_index[i + 1]
|
| 264 |
+
|
| 265 |
+
if should_terminate:
|
| 266 |
+
# Close the chain.
|
| 267 |
+
chain_end = "TER"
|
| 268 |
+
chain_termination_line = (
|
| 269 |
+
f"{chain_end:<6}{atom_index:>5} {res_1to3(aatype[i]):>3} {chain_tag:>1}{residue_index[i]:>4}"
|
| 270 |
+
)
|
| 271 |
+
pdb_lines.append(chain_termination_line)
|
| 272 |
+
atom_index += 1
|
| 273 |
+
|
| 274 |
+
if i != n - 1:
|
| 275 |
+
# "prev" is a misnomer here. This happens at the beginning of
|
| 276 |
+
# each new chain.
|
| 277 |
+
pdb_lines.extend(get_pdb_headers(prot, prev_chain_index))
|
| 278 |
+
|
| 279 |
+
pdb_lines.append("END")
|
| 280 |
+
pdb_lines.append("")
|
| 281 |
+
return "\n".join(pdb_lines)
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
def ideal_atom_mask(prot: Protein) -> np.ndarray:
|
| 285 |
+
"""Computes an ideal atom mask.
|
| 286 |
+
|
| 287 |
+
`Protein.atom_mask` typically is defined according to the atoms that are reported in the PDB. This function
|
| 288 |
+
computes a mask according to heavy atoms that should be present in the given sequence of amino acids.
|
| 289 |
+
|
| 290 |
+
Args:
|
| 291 |
+
prot: `Protein` whose fields are `numpy.ndarray` objects.
|
| 292 |
+
|
| 293 |
+
Returns:
|
| 294 |
+
An ideal atom mask.
|
| 295 |
+
"""
|
| 296 |
+
return residue_constants.STANDARD_ATOM_MASK[prot.aatype]
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
def from_prediction(
|
| 300 |
+
features: FeatureDict,
|
| 301 |
+
result: ModelOutput,
|
| 302 |
+
b_factors: np.ndarray | None = None,
|
| 303 |
+
chain_index: np.ndarray | None = None,
|
| 304 |
+
remark: str | None = None,
|
| 305 |
+
parents: Sequence[str] | None = None,
|
| 306 |
+
parents_chain_index: Sequence[int] | None = None,
|
| 307 |
+
) -> Protein:
|
| 308 |
+
"""Assembles a protein from a prediction.
|
| 309 |
+
|
| 310 |
+
Args:
|
| 311 |
+
features: Dictionary holding model inputs.
|
| 312 |
+
result: Dictionary holding model outputs.
|
| 313 |
+
b_factors: (Optional) B-factors to use for the protein.
|
| 314 |
+
chain_index: (Optional) Chain indices for multi-chain predictions
|
| 315 |
+
remark: (Optional) Remark about the prediction
|
| 316 |
+
parents: (Optional) List of template names
|
| 317 |
+
Returns:
|
| 318 |
+
A protein instance.
|
| 319 |
+
"""
|
| 320 |
+
return Protein(
|
| 321 |
+
aatype=features["aatype"],
|
| 322 |
+
atom_positions=result["final_atom_positions"],
|
| 323 |
+
atom_mask=result["final_atom_mask"],
|
| 324 |
+
residue_index=features["residue_index"] + 1,
|
| 325 |
+
b_factors=b_factors if b_factors is not None else np.zeros_like(result["final_atom_mask"]),
|
| 326 |
+
chain_index=chain_index,
|
| 327 |
+
remark=remark,
|
| 328 |
+
parents=parents,
|
| 329 |
+
parents_chain_index=parents_chain_index,
|
| 330 |
+
)
|
LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/esm/openfold_utils/residue_constants.py
ADDED
|
@@ -0,0 +1,979 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2021 AlQuraishi Laboratory
|
| 2 |
+
# Copyright 2021 DeepMind Technologies Limited
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
"""Constants used in AlphaFold."""
|
| 17 |
+
|
| 18 |
+
import collections
|
| 19 |
+
import copy
|
| 20 |
+
import functools
|
| 21 |
+
from collections.abc import Mapping, Sequence
|
| 22 |
+
from importlib import resources
|
| 23 |
+
|
| 24 |
+
import numpy as np
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
# Internal import (35fd).
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
# Distance from one CA to next CA [trans configuration: omega = 180].
|
| 31 |
+
ca_ca = 3.80209737096
|
| 32 |
+
|
| 33 |
+
# Format: The list for each AA type contains chi1, chi2, chi3, chi4 in
|
| 34 |
+
# this order (or a relevant subset from chi1 onwards). ALA and GLY don't have
|
| 35 |
+
# chi angles so their chi angle lists are empty.
|
| 36 |
+
chi_angles_atoms: dict[str, list[list[str]]] = {
|
| 37 |
+
"ALA": [],
|
| 38 |
+
# Chi5 in arginine is always 0 +- 5 degrees, so ignore it.
|
| 39 |
+
"ARG": [["N", "CA", "CB", "CG"], ["CA", "CB", "CG", "CD"], ["CB", "CG", "CD", "NE"], ["CG", "CD", "NE", "CZ"]],
|
| 40 |
+
"ASN": [["N", "CA", "CB", "CG"], ["CA", "CB", "CG", "OD1"]],
|
| 41 |
+
"ASP": [["N", "CA", "CB", "CG"], ["CA", "CB", "CG", "OD1"]],
|
| 42 |
+
"CYS": [["N", "CA", "CB", "SG"]],
|
| 43 |
+
"GLN": [["N", "CA", "CB", "CG"], ["CA", "CB", "CG", "CD"], ["CB", "CG", "CD", "OE1"]],
|
| 44 |
+
"GLU": [["N", "CA", "CB", "CG"], ["CA", "CB", "CG", "CD"], ["CB", "CG", "CD", "OE1"]],
|
| 45 |
+
"GLY": [],
|
| 46 |
+
"HIS": [["N", "CA", "CB", "CG"], ["CA", "CB", "CG", "ND1"]],
|
| 47 |
+
"ILE": [["N", "CA", "CB", "CG1"], ["CA", "CB", "CG1", "CD1"]],
|
| 48 |
+
"LEU": [["N", "CA", "CB", "CG"], ["CA", "CB", "CG", "CD1"]],
|
| 49 |
+
"LYS": [["N", "CA", "CB", "CG"], ["CA", "CB", "CG", "CD"], ["CB", "CG", "CD", "CE"], ["CG", "CD", "CE", "NZ"]],
|
| 50 |
+
"MET": [["N", "CA", "CB", "CG"], ["CA", "CB", "CG", "SD"], ["CB", "CG", "SD", "CE"]],
|
| 51 |
+
"PHE": [["N", "CA", "CB", "CG"], ["CA", "CB", "CG", "CD1"]],
|
| 52 |
+
"PRO": [["N", "CA", "CB", "CG"], ["CA", "CB", "CG", "CD"]],
|
| 53 |
+
"SER": [["N", "CA", "CB", "OG"]],
|
| 54 |
+
"THR": [["N", "CA", "CB", "OG1"]],
|
| 55 |
+
"TRP": [["N", "CA", "CB", "CG"], ["CA", "CB", "CG", "CD1"]],
|
| 56 |
+
"TYR": [["N", "CA", "CB", "CG"], ["CA", "CB", "CG", "CD1"]],
|
| 57 |
+
"VAL": [["N", "CA", "CB", "CG1"]],
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
# If chi angles given in fixed-length array, this matrix determines how to mask
|
| 61 |
+
# them for each AA type. The order is as per restype_order (see below).
|
| 62 |
+
chi_angles_mask: list[list[float]] = [
|
| 63 |
+
[0.0, 0.0, 0.0, 0.0], # ALA
|
| 64 |
+
[1.0, 1.0, 1.0, 1.0], # ARG
|
| 65 |
+
[1.0, 1.0, 0.0, 0.0], # ASN
|
| 66 |
+
[1.0, 1.0, 0.0, 0.0], # ASP
|
| 67 |
+
[1.0, 0.0, 0.0, 0.0], # CYS
|
| 68 |
+
[1.0, 1.0, 1.0, 0.0], # GLN
|
| 69 |
+
[1.0, 1.0, 1.0, 0.0], # GLU
|
| 70 |
+
[0.0, 0.0, 0.0, 0.0], # GLY
|
| 71 |
+
[1.0, 1.0, 0.0, 0.0], # HIS
|
| 72 |
+
[1.0, 1.0, 0.0, 0.0], # ILE
|
| 73 |
+
[1.0, 1.0, 0.0, 0.0], # LEU
|
| 74 |
+
[1.0, 1.0, 1.0, 1.0], # LYS
|
| 75 |
+
[1.0, 1.0, 1.0, 0.0], # MET
|
| 76 |
+
[1.0, 1.0, 0.0, 0.0], # PHE
|
| 77 |
+
[1.0, 1.0, 0.0, 0.0], # PRO
|
| 78 |
+
[1.0, 0.0, 0.0, 0.0], # SER
|
| 79 |
+
[1.0, 0.0, 0.0, 0.0], # THR
|
| 80 |
+
[1.0, 1.0, 0.0, 0.0], # TRP
|
| 81 |
+
[1.0, 1.0, 0.0, 0.0], # TYR
|
| 82 |
+
[1.0, 0.0, 0.0, 0.0], # VAL
|
| 83 |
+
]
|
| 84 |
+
|
| 85 |
+
# The following chi angles are pi periodic: they can be rotated by a multiple
|
| 86 |
+
# of pi without affecting the structure.
|
| 87 |
+
chi_pi_periodic: list[list[float]] = [
|
| 88 |
+
[0.0, 0.0, 0.0, 0.0], # ALA
|
| 89 |
+
[0.0, 0.0, 0.0, 0.0], # ARG
|
| 90 |
+
[0.0, 0.0, 0.0, 0.0], # ASN
|
| 91 |
+
[0.0, 1.0, 0.0, 0.0], # ASP
|
| 92 |
+
[0.0, 0.0, 0.0, 0.0], # CYS
|
| 93 |
+
[0.0, 0.0, 0.0, 0.0], # GLN
|
| 94 |
+
[0.0, 0.0, 1.0, 0.0], # GLU
|
| 95 |
+
[0.0, 0.0, 0.0, 0.0], # GLY
|
| 96 |
+
[0.0, 0.0, 0.0, 0.0], # HIS
|
| 97 |
+
[0.0, 0.0, 0.0, 0.0], # ILE
|
| 98 |
+
[0.0, 0.0, 0.0, 0.0], # LEU
|
| 99 |
+
[0.0, 0.0, 0.0, 0.0], # LYS
|
| 100 |
+
[0.0, 0.0, 0.0, 0.0], # MET
|
| 101 |
+
[0.0, 1.0, 0.0, 0.0], # PHE
|
| 102 |
+
[0.0, 0.0, 0.0, 0.0], # PRO
|
| 103 |
+
[0.0, 0.0, 0.0, 0.0], # SER
|
| 104 |
+
[0.0, 0.0, 0.0, 0.0], # THR
|
| 105 |
+
[0.0, 0.0, 0.0, 0.0], # TRP
|
| 106 |
+
[0.0, 1.0, 0.0, 0.0], # TYR
|
| 107 |
+
[0.0, 0.0, 0.0, 0.0], # VAL
|
| 108 |
+
[0.0, 0.0, 0.0, 0.0], # UNK
|
| 109 |
+
]
|
| 110 |
+
|
| 111 |
+
# Atoms positions relative to the 8 rigid groups, defined by the pre-omega, phi,
|
| 112 |
+
# psi and chi angles:
|
| 113 |
+
# 0: 'backbone group',
|
| 114 |
+
# 1: 'pre-omega-group', (empty)
|
| 115 |
+
# 2: 'phi-group', (currently empty, because it defines only hydrogens)
|
| 116 |
+
# 3: 'psi-group',
|
| 117 |
+
# 4,5,6,7: 'chi1,2,3,4-group'
|
| 118 |
+
# The atom positions are relative to the axis-end-atom of the corresponding
|
| 119 |
+
# rotation axis. The x-axis is in direction of the rotation axis, and the y-axis
|
| 120 |
+
# is defined such that the dihedral-angle-definiting atom (the last entry in
|
| 121 |
+
# chi_angles_atoms above) is in the xy-plane (with a positive y-coordinate).
|
| 122 |
+
# format: [atomname, group_idx, rel_position]
|
| 123 |
+
rigid_group_atom_positions: dict[str, list[tuple[str, int, tuple[float, float, float]]]] = {
|
| 124 |
+
"ALA": [
|
| 125 |
+
("N", 0, (-0.525, 1.363, 0.000)),
|
| 126 |
+
("CA", 0, (0.000, 0.000, 0.000)),
|
| 127 |
+
("C", 0, (1.526, -0.000, -0.000)),
|
| 128 |
+
("CB", 0, (-0.529, -0.774, -1.205)),
|
| 129 |
+
("O", 3, (0.627, 1.062, 0.000)),
|
| 130 |
+
],
|
| 131 |
+
"ARG": [
|
| 132 |
+
("N", 0, (-0.524, 1.362, -0.000)),
|
| 133 |
+
("CA", 0, (0.000, 0.000, 0.000)),
|
| 134 |
+
("C", 0, (1.525, -0.000, -0.000)),
|
| 135 |
+
("CB", 0, (-0.524, -0.778, -1.209)),
|
| 136 |
+
("O", 3, (0.626, 1.062, 0.000)),
|
| 137 |
+
("CG", 4, (0.616, 1.390, -0.000)),
|
| 138 |
+
("CD", 5, (0.564, 1.414, 0.000)),
|
| 139 |
+
("NE", 6, (0.539, 1.357, -0.000)),
|
| 140 |
+
("NH1", 7, (0.206, 2.301, 0.000)),
|
| 141 |
+
("NH2", 7, (2.078, 0.978, -0.000)),
|
| 142 |
+
("CZ", 7, (0.758, 1.093, -0.000)),
|
| 143 |
+
],
|
| 144 |
+
"ASN": [
|
| 145 |
+
("N", 0, (-0.536, 1.357, 0.000)),
|
| 146 |
+
("CA", 0, (0.000, 0.000, 0.000)),
|
| 147 |
+
("C", 0, (1.526, -0.000, -0.000)),
|
| 148 |
+
("CB", 0, (-0.531, -0.787, -1.200)),
|
| 149 |
+
("O", 3, (0.625, 1.062, 0.000)),
|
| 150 |
+
("CG", 4, (0.584, 1.399, 0.000)),
|
| 151 |
+
("ND2", 5, (0.593, -1.188, 0.001)),
|
| 152 |
+
("OD1", 5, (0.633, 1.059, 0.000)),
|
| 153 |
+
],
|
| 154 |
+
"ASP": [
|
| 155 |
+
("N", 0, (-0.525, 1.362, -0.000)),
|
| 156 |
+
("CA", 0, (0.000, 0.000, 0.000)),
|
| 157 |
+
("C", 0, (1.527, 0.000, -0.000)),
|
| 158 |
+
("CB", 0, (-0.526, -0.778, -1.208)),
|
| 159 |
+
("O", 3, (0.626, 1.062, -0.000)),
|
| 160 |
+
("CG", 4, (0.593, 1.398, -0.000)),
|
| 161 |
+
("OD1", 5, (0.610, 1.091, 0.000)),
|
| 162 |
+
("OD2", 5, (0.592, -1.101, -0.003)),
|
| 163 |
+
],
|
| 164 |
+
"CYS": [
|
| 165 |
+
("N", 0, (-0.522, 1.362, -0.000)),
|
| 166 |
+
("CA", 0, (0.000, 0.000, 0.000)),
|
| 167 |
+
("C", 0, (1.524, 0.000, 0.000)),
|
| 168 |
+
("CB", 0, (-0.519, -0.773, -1.212)),
|
| 169 |
+
("O", 3, (0.625, 1.062, -0.000)),
|
| 170 |
+
("SG", 4, (0.728, 1.653, 0.000)),
|
| 171 |
+
],
|
| 172 |
+
"GLN": [
|
| 173 |
+
("N", 0, (-0.526, 1.361, -0.000)),
|
| 174 |
+
("CA", 0, (0.000, 0.000, 0.000)),
|
| 175 |
+
("C", 0, (1.526, 0.000, 0.000)),
|
| 176 |
+
("CB", 0, (-0.525, -0.779, -1.207)),
|
| 177 |
+
("O", 3, (0.626, 1.062, -0.000)),
|
| 178 |
+
("CG", 4, (0.615, 1.393, 0.000)),
|
| 179 |
+
("CD", 5, (0.587, 1.399, -0.000)),
|
| 180 |
+
("NE2", 6, (0.593, -1.189, -0.001)),
|
| 181 |
+
("OE1", 6, (0.634, 1.060, 0.000)),
|
| 182 |
+
],
|
| 183 |
+
"GLU": [
|
| 184 |
+
("N", 0, (-0.528, 1.361, 0.000)),
|
| 185 |
+
("CA", 0, (0.000, 0.000, 0.000)),
|
| 186 |
+
("C", 0, (1.526, -0.000, -0.000)),
|
| 187 |
+
("CB", 0, (-0.526, -0.781, -1.207)),
|
| 188 |
+
("O", 3, (0.626, 1.062, 0.000)),
|
| 189 |
+
("CG", 4, (0.615, 1.392, 0.000)),
|
| 190 |
+
("CD", 5, (0.600, 1.397, 0.000)),
|
| 191 |
+
("OE1", 6, (0.607, 1.095, -0.000)),
|
| 192 |
+
("OE2", 6, (0.589, -1.104, -0.001)),
|
| 193 |
+
],
|
| 194 |
+
"GLY": [
|
| 195 |
+
("N", 0, (-0.572, 1.337, 0.000)),
|
| 196 |
+
("CA", 0, (0.000, 0.000, 0.000)),
|
| 197 |
+
("C", 0, (1.517, -0.000, -0.000)),
|
| 198 |
+
("O", 3, (0.626, 1.062, -0.000)),
|
| 199 |
+
],
|
| 200 |
+
"HIS": [
|
| 201 |
+
("N", 0, (-0.527, 1.360, 0.000)),
|
| 202 |
+
("CA", 0, (0.000, 0.000, 0.000)),
|
| 203 |
+
("C", 0, (1.525, 0.000, 0.000)),
|
| 204 |
+
("CB", 0, (-0.525, -0.778, -1.208)),
|
| 205 |
+
("O", 3, (0.625, 1.063, 0.000)),
|
| 206 |
+
("CG", 4, (0.600, 1.370, -0.000)),
|
| 207 |
+
("CD2", 5, (0.889, -1.021, 0.003)),
|
| 208 |
+
("ND1", 5, (0.744, 1.160, -0.000)),
|
| 209 |
+
("CE1", 5, (2.030, 0.851, 0.002)),
|
| 210 |
+
("NE2", 5, (2.145, -0.466, 0.004)),
|
| 211 |
+
],
|
| 212 |
+
"ILE": [
|
| 213 |
+
("N", 0, (-0.493, 1.373, -0.000)),
|
| 214 |
+
("CA", 0, (0.000, 0.000, 0.000)),
|
| 215 |
+
("C", 0, (1.527, -0.000, -0.000)),
|
| 216 |
+
("CB", 0, (-0.536, -0.793, -1.213)),
|
| 217 |
+
("O", 3, (0.627, 1.062, -0.000)),
|
| 218 |
+
("CG1", 4, (0.534, 1.437, -0.000)),
|
| 219 |
+
("CG2", 4, (0.540, -0.785, -1.199)),
|
| 220 |
+
("CD1", 5, (0.619, 1.391, 0.000)),
|
| 221 |
+
],
|
| 222 |
+
"LEU": [
|
| 223 |
+
("N", 0, (-0.520, 1.363, 0.000)),
|
| 224 |
+
("CA", 0, (0.000, 0.000, 0.000)),
|
| 225 |
+
("C", 0, (1.525, -0.000, -0.000)),
|
| 226 |
+
("CB", 0, (-0.522, -0.773, -1.214)),
|
| 227 |
+
("O", 3, (0.625, 1.063, -0.000)),
|
| 228 |
+
("CG", 4, (0.678, 1.371, 0.000)),
|
| 229 |
+
("CD1", 5, (0.530, 1.430, -0.000)),
|
| 230 |
+
("CD2", 5, (0.535, -0.774, 1.200)),
|
| 231 |
+
],
|
| 232 |
+
"LYS": [
|
| 233 |
+
("N", 0, (-0.526, 1.362, -0.000)),
|
| 234 |
+
("CA", 0, (0.000, 0.000, 0.000)),
|
| 235 |
+
("C", 0, (1.526, 0.000, 0.000)),
|
| 236 |
+
("CB", 0, (-0.524, -0.778, -1.208)),
|
| 237 |
+
("O", 3, (0.626, 1.062, -0.000)),
|
| 238 |
+
("CG", 4, (0.619, 1.390, 0.000)),
|
| 239 |
+
("CD", 5, (0.559, 1.417, 0.000)),
|
| 240 |
+
("CE", 6, (0.560, 1.416, 0.000)),
|
| 241 |
+
("NZ", 7, (0.554, 1.387, 0.000)),
|
| 242 |
+
],
|
| 243 |
+
"MET": [
|
| 244 |
+
("N", 0, (-0.521, 1.364, -0.000)),
|
| 245 |
+
("CA", 0, (0.000, 0.000, 0.000)),
|
| 246 |
+
("C", 0, (1.525, 0.000, 0.000)),
|
| 247 |
+
("CB", 0, (-0.523, -0.776, -1.210)),
|
| 248 |
+
("O", 3, (0.625, 1.062, -0.000)),
|
| 249 |
+
("CG", 4, (0.613, 1.391, -0.000)),
|
| 250 |
+
("SD", 5, (0.703, 1.695, 0.000)),
|
| 251 |
+
("CE", 6, (0.320, 1.786, -0.000)),
|
| 252 |
+
],
|
| 253 |
+
"PHE": [
|
| 254 |
+
("N", 0, (-0.518, 1.363, 0.000)),
|
| 255 |
+
("CA", 0, (0.000, 0.000, 0.000)),
|
| 256 |
+
("C", 0, (1.524, 0.000, -0.000)),
|
| 257 |
+
("CB", 0, (-0.525, -0.776, -1.212)),
|
| 258 |
+
("O", 3, (0.626, 1.062, -0.000)),
|
| 259 |
+
("CG", 4, (0.607, 1.377, 0.000)),
|
| 260 |
+
("CD1", 5, (0.709, 1.195, -0.000)),
|
| 261 |
+
("CD2", 5, (0.706, -1.196, 0.000)),
|
| 262 |
+
("CE1", 5, (2.102, 1.198, -0.000)),
|
| 263 |
+
("CE2", 5, (2.098, -1.201, -0.000)),
|
| 264 |
+
("CZ", 5, (2.794, -0.003, -0.001)),
|
| 265 |
+
],
|
| 266 |
+
"PRO": [
|
| 267 |
+
("N", 0, (-0.566, 1.351, -0.000)),
|
| 268 |
+
("CA", 0, (0.000, 0.000, 0.000)),
|
| 269 |
+
("C", 0, (1.527, -0.000, 0.000)),
|
| 270 |
+
("CB", 0, (-0.546, -0.611, -1.293)),
|
| 271 |
+
("O", 3, (0.621, 1.066, 0.000)),
|
| 272 |
+
("CG", 4, (0.382, 1.445, 0.0)),
|
| 273 |
+
# ('CD', 5, (0.427, 1.440, 0.0)),
|
| 274 |
+
("CD", 5, (0.477, 1.424, 0.0)), # manually made angle 2 degrees larger
|
| 275 |
+
],
|
| 276 |
+
"SER": [
|
| 277 |
+
("N", 0, (-0.529, 1.360, -0.000)),
|
| 278 |
+
("CA", 0, (0.000, 0.000, 0.000)),
|
| 279 |
+
("C", 0, (1.525, -0.000, -0.000)),
|
| 280 |
+
("CB", 0, (-0.518, -0.777, -1.211)),
|
| 281 |
+
("O", 3, (0.626, 1.062, -0.000)),
|
| 282 |
+
("OG", 4, (0.503, 1.325, 0.000)),
|
| 283 |
+
],
|
| 284 |
+
"THR": [
|
| 285 |
+
("N", 0, (-0.517, 1.364, 0.000)),
|
| 286 |
+
("CA", 0, (0.000, 0.000, 0.000)),
|
| 287 |
+
("C", 0, (1.526, 0.000, -0.000)),
|
| 288 |
+
("CB", 0, (-0.516, -0.793, -1.215)),
|
| 289 |
+
("O", 3, (0.626, 1.062, 0.000)),
|
| 290 |
+
("CG2", 4, (0.550, -0.718, -1.228)),
|
| 291 |
+
("OG1", 4, (0.472, 1.353, 0.000)),
|
| 292 |
+
],
|
| 293 |
+
"TRP": [
|
| 294 |
+
("N", 0, (-0.521, 1.363, 0.000)),
|
| 295 |
+
("CA", 0, (0.000, 0.000, 0.000)),
|
| 296 |
+
("C", 0, (1.525, -0.000, 0.000)),
|
| 297 |
+
("CB", 0, (-0.523, -0.776, -1.212)),
|
| 298 |
+
("O", 3, (0.627, 1.062, 0.000)),
|
| 299 |
+
("CG", 4, (0.609, 1.370, -0.000)),
|
| 300 |
+
("CD1", 5, (0.824, 1.091, 0.000)),
|
| 301 |
+
("CD2", 5, (0.854, -1.148, -0.005)),
|
| 302 |
+
("CE2", 5, (2.186, -0.678, -0.007)),
|
| 303 |
+
("CE3", 5, (0.622, -2.530, -0.007)),
|
| 304 |
+
("NE1", 5, (2.140, 0.690, -0.004)),
|
| 305 |
+
("CH2", 5, (3.028, -2.890, -0.013)),
|
| 306 |
+
("CZ2", 5, (3.283, -1.543, -0.011)),
|
| 307 |
+
("CZ3", 5, (1.715, -3.389, -0.011)),
|
| 308 |
+
],
|
| 309 |
+
"TYR": [
|
| 310 |
+
("N", 0, (-0.522, 1.362, 0.000)),
|
| 311 |
+
("CA", 0, (0.000, 0.000, 0.000)),
|
| 312 |
+
("C", 0, (1.524, -0.000, -0.000)),
|
| 313 |
+
("CB", 0, (-0.522, -0.776, -1.213)),
|
| 314 |
+
("O", 3, (0.627, 1.062, -0.000)),
|
| 315 |
+
("CG", 4, (0.607, 1.382, -0.000)),
|
| 316 |
+
("CD1", 5, (0.716, 1.195, -0.000)),
|
| 317 |
+
("CD2", 5, (0.713, -1.194, -0.001)),
|
| 318 |
+
("CE1", 5, (2.107, 1.200, -0.002)),
|
| 319 |
+
("CE2", 5, (2.104, -1.201, -0.003)),
|
| 320 |
+
("OH", 5, (4.168, -0.002, -0.005)),
|
| 321 |
+
("CZ", 5, (2.791, -0.001, -0.003)),
|
| 322 |
+
],
|
| 323 |
+
"VAL": [
|
| 324 |
+
("N", 0, (-0.494, 1.373, -0.000)),
|
| 325 |
+
("CA", 0, (0.000, 0.000, 0.000)),
|
| 326 |
+
("C", 0, (1.527, -0.000, -0.000)),
|
| 327 |
+
("CB", 0, (-0.533, -0.795, -1.213)),
|
| 328 |
+
("O", 3, (0.627, 1.062, -0.000)),
|
| 329 |
+
("CG1", 4, (0.540, 1.429, -0.000)),
|
| 330 |
+
("CG2", 4, (0.533, -0.776, 1.203)),
|
| 331 |
+
],
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
# A list of atoms (excluding hydrogen) for each AA type. PDB naming convention.
|
| 335 |
+
residue_atoms: dict[str, list[str]] = {
|
| 336 |
+
"ALA": ["C", "CA", "CB", "N", "O"],
|
| 337 |
+
"ARG": ["C", "CA", "CB", "CG", "CD", "CZ", "N", "NE", "O", "NH1", "NH2"],
|
| 338 |
+
"ASP": ["C", "CA", "CB", "CG", "N", "O", "OD1", "OD2"],
|
| 339 |
+
"ASN": ["C", "CA", "CB", "CG", "N", "ND2", "O", "OD1"],
|
| 340 |
+
"CYS": ["C", "CA", "CB", "N", "O", "SG"],
|
| 341 |
+
"GLU": ["C", "CA", "CB", "CG", "CD", "N", "O", "OE1", "OE2"],
|
| 342 |
+
"GLN": ["C", "CA", "CB", "CG", "CD", "N", "NE2", "O", "OE1"],
|
| 343 |
+
"GLY": ["C", "CA", "N", "O"],
|
| 344 |
+
"HIS": ["C", "CA", "CB", "CG", "CD2", "CE1", "N", "ND1", "NE2", "O"],
|
| 345 |
+
"ILE": ["C", "CA", "CB", "CG1", "CG2", "CD1", "N", "O"],
|
| 346 |
+
"LEU": ["C", "CA", "CB", "CG", "CD1", "CD2", "N", "O"],
|
| 347 |
+
"LYS": ["C", "CA", "CB", "CG", "CD", "CE", "N", "NZ", "O"],
|
| 348 |
+
"MET": ["C", "CA", "CB", "CG", "CE", "N", "O", "SD"],
|
| 349 |
+
"PHE": ["C", "CA", "CB", "CG", "CD1", "CD2", "CE1", "CE2", "CZ", "N", "O"],
|
| 350 |
+
"PRO": ["C", "CA", "CB", "CG", "CD", "N", "O"],
|
| 351 |
+
"SER": ["C", "CA", "CB", "N", "O", "OG"],
|
| 352 |
+
"THR": ["C", "CA", "CB", "CG2", "N", "O", "OG1"],
|
| 353 |
+
"TRP": ["C", "CA", "CB", "CG", "CD1", "CD2", "CE2", "CE3", "CZ2", "CZ3", "CH2", "N", "NE1", "O"],
|
| 354 |
+
"TYR": ["C", "CA", "CB", "CG", "CD1", "CD2", "CE1", "CE2", "CZ", "N", "O", "OH"],
|
| 355 |
+
"VAL": ["C", "CA", "CB", "CG1", "CG2", "N", "O"],
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
# Naming swaps for ambiguous atom names.
|
| 359 |
+
# Due to symmetries in the amino acids the naming of atoms is ambiguous in
|
| 360 |
+
# 4 of the 20 amino acids.
|
| 361 |
+
# (The LDDT paper lists 7 amino acids as ambiguous, but the naming ambiguities
|
| 362 |
+
# in LEU, VAL and ARG can be resolved by using the 3d constellations of
|
| 363 |
+
# the 'ambiguous' atoms and their neighbours)
|
| 364 |
+
# TODO: ^ interpret this
|
| 365 |
+
residue_atom_renaming_swaps: dict[str, dict[str, str]] = {
|
| 366 |
+
"ASP": {"OD1": "OD2"},
|
| 367 |
+
"GLU": {"OE1": "OE2"},
|
| 368 |
+
"PHE": {"CD1": "CD2", "CE1": "CE2"},
|
| 369 |
+
"TYR": {"CD1": "CD2", "CE1": "CE2"},
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
# Van der Waals radii [Angstroem] of the atoms (from Wikipedia)
|
| 373 |
+
van_der_waals_radius: dict[str, float] = {
|
| 374 |
+
"C": 1.7,
|
| 375 |
+
"N": 1.55,
|
| 376 |
+
"O": 1.52,
|
| 377 |
+
"S": 1.8,
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
Bond = collections.namedtuple("Bond", ["atom1_name", "atom2_name", "length", "stddev"])
|
| 381 |
+
BondAngle = collections.namedtuple(
|
| 382 |
+
"BondAngle",
|
| 383 |
+
["atom1_name", "atom2_name", "atom3name", "angle_rad", "stddev"],
|
| 384 |
+
)
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
def map_structure_with_atom_order(in_list: list, first_call: bool = True) -> list:
|
| 388 |
+
# Maps strings in a nested list structure to their corresponding index in atom_order
|
| 389 |
+
if first_call:
|
| 390 |
+
in_list = copy.deepcopy(in_list)
|
| 391 |
+
for i in range(len(in_list)):
|
| 392 |
+
if isinstance(in_list[i], list):
|
| 393 |
+
in_list[i] = map_structure_with_atom_order(in_list[i], first_call=False)
|
| 394 |
+
elif isinstance(in_list[i], str):
|
| 395 |
+
in_list[i] = atom_order[in_list[i]]
|
| 396 |
+
else:
|
| 397 |
+
raise TypeError("Unexpected type when mapping nested lists!")
|
| 398 |
+
return in_list
|
| 399 |
+
|
| 400 |
+
|
| 401 |
+
@functools.cache
|
| 402 |
+
def load_stereo_chemical_props() -> tuple[
|
| 403 |
+
Mapping[str, list[Bond]],
|
| 404 |
+
Mapping[str, list[Bond]],
|
| 405 |
+
Mapping[str, list[BondAngle]],
|
| 406 |
+
]:
|
| 407 |
+
"""Load stereo_chemical_props.txt into a nice structure.
|
| 408 |
+
|
| 409 |
+
Load literature values for bond lengths and bond angles and translate bond angles into the length of the opposite
|
| 410 |
+
edge of the triangle ("residue_virtual_bonds").
|
| 411 |
+
|
| 412 |
+
Returns:
|
| 413 |
+
residue_bonds: dict that maps resname --> list of Bond tuples residue_virtual_bonds: dict that maps resname -->
|
| 414 |
+
list of Bond tuples residue_bond_angles: dict that maps resname --> list of BondAngle tuples
|
| 415 |
+
"""
|
| 416 |
+
# TODO: this file should be downloaded in a setup script
|
| 417 |
+
stereo_chemical_props = resources.read_text("openfold.resources", "stereo_chemical_props.txt")
|
| 418 |
+
|
| 419 |
+
lines_iter = iter(stereo_chemical_props.splitlines())
|
| 420 |
+
# Load bond lengths.
|
| 421 |
+
residue_bonds: dict[str, list[Bond]] = {}
|
| 422 |
+
next(lines_iter) # Skip header line.
|
| 423 |
+
for line in lines_iter:
|
| 424 |
+
if line.strip() == "-":
|
| 425 |
+
break
|
| 426 |
+
bond, resname, bond_length, stddev = line.split()
|
| 427 |
+
atom1, atom2 = bond.split("-")
|
| 428 |
+
if resname not in residue_bonds:
|
| 429 |
+
residue_bonds[resname] = []
|
| 430 |
+
residue_bonds[resname].append(Bond(atom1, atom2, float(bond_length), float(stddev)))
|
| 431 |
+
residue_bonds["UNK"] = []
|
| 432 |
+
|
| 433 |
+
# Load bond angles.
|
| 434 |
+
residue_bond_angles: dict[str, list[BondAngle]] = {}
|
| 435 |
+
next(lines_iter) # Skip empty line.
|
| 436 |
+
next(lines_iter) # Skip header line.
|
| 437 |
+
for line in lines_iter:
|
| 438 |
+
if line.strip() == "-":
|
| 439 |
+
break
|
| 440 |
+
bond, resname, angle_degree, stddev_degree = line.split()
|
| 441 |
+
atom1, atom2, atom3 = bond.split("-")
|
| 442 |
+
if resname not in residue_bond_angles:
|
| 443 |
+
residue_bond_angles[resname] = []
|
| 444 |
+
residue_bond_angles[resname].append(
|
| 445 |
+
BondAngle(
|
| 446 |
+
atom1,
|
| 447 |
+
atom2,
|
| 448 |
+
atom3,
|
| 449 |
+
float(angle_degree) / 180.0 * np.pi,
|
| 450 |
+
float(stddev_degree) / 180.0 * np.pi,
|
| 451 |
+
)
|
| 452 |
+
)
|
| 453 |
+
residue_bond_angles["UNK"] = []
|
| 454 |
+
|
| 455 |
+
def make_bond_key(atom1_name: str, atom2_name: str) -> str:
|
| 456 |
+
"""Unique key to lookup bonds."""
|
| 457 |
+
return "-".join(sorted([atom1_name, atom2_name]))
|
| 458 |
+
|
| 459 |
+
# Translate bond angles into distances ("virtual bonds").
|
| 460 |
+
residue_virtual_bonds: dict[str, list[Bond]] = {}
|
| 461 |
+
for resname, bond_angles in residue_bond_angles.items():
|
| 462 |
+
# Create a fast lookup dict for bond lengths.
|
| 463 |
+
bond_cache: dict[str, Bond] = {}
|
| 464 |
+
for b in residue_bonds[resname]:
|
| 465 |
+
bond_cache[make_bond_key(b.atom1_name, b.atom2_name)] = b
|
| 466 |
+
residue_virtual_bonds[resname] = []
|
| 467 |
+
for ba in bond_angles:
|
| 468 |
+
bond1 = bond_cache[make_bond_key(ba.atom1_name, ba.atom2_name)]
|
| 469 |
+
bond2 = bond_cache[make_bond_key(ba.atom2_name, ba.atom3name)]
|
| 470 |
+
|
| 471 |
+
# Compute distance between atom1 and atom3 using the law of cosines
|
| 472 |
+
# c^2 = a^2 + b^2 - 2ab*cos(gamma).
|
| 473 |
+
gamma = ba.angle_rad
|
| 474 |
+
length = np.sqrt(bond1.length**2 + bond2.length**2 - 2 * bond1.length * bond2.length * np.cos(gamma))
|
| 475 |
+
|
| 476 |
+
# Propagation of uncertainty assuming uncorrelated errors.
|
| 477 |
+
dl_outer = 0.5 / length
|
| 478 |
+
dl_dgamma = (2 * bond1.length * bond2.length * np.sin(gamma)) * dl_outer
|
| 479 |
+
dl_db1 = (2 * bond1.length - 2 * bond2.length * np.cos(gamma)) * dl_outer
|
| 480 |
+
dl_db2 = (2 * bond2.length - 2 * bond1.length * np.cos(gamma)) * dl_outer
|
| 481 |
+
stddev = np.sqrt(
|
| 482 |
+
(dl_dgamma * ba.stddev) ** 2 + (dl_db1 * bond1.stddev) ** 2 + (dl_db2 * bond2.stddev) ** 2
|
| 483 |
+
)
|
| 484 |
+
residue_virtual_bonds[resname].append(Bond(ba.atom1_name, ba.atom3name, length, stddev))
|
| 485 |
+
|
| 486 |
+
return (residue_bonds, residue_virtual_bonds, residue_bond_angles)
|
| 487 |
+
|
| 488 |
+
|
| 489 |
+
# Between-residue bond lengths for general bonds (first element) and for Proline
|
| 490 |
+
# (second element).
|
| 491 |
+
between_res_bond_length_c_n: tuple[float, float] = (1.329, 1.341)
|
| 492 |
+
between_res_bond_length_stddev_c_n: tuple[float, float] = (0.014, 0.016)
|
| 493 |
+
|
| 494 |
+
# Between-residue cos_angles.
|
| 495 |
+
between_res_cos_angles_c_n_ca: tuple[float, float] = (-0.5203, 0.0353) # degrees: 121.352 +- 2.315
|
| 496 |
+
between_res_cos_angles_ca_c_n: tuple[float, float] = (-0.4473, 0.0311) # degrees: 116.568 +- 1.995
|
| 497 |
+
|
| 498 |
+
# This mapping is used when we need to store atom data in a format that requires
|
| 499 |
+
# fixed atom data size for every residue (e.g. a numpy array).
|
| 500 |
+
atom_types: list[str] = [
|
| 501 |
+
"N",
|
| 502 |
+
"CA",
|
| 503 |
+
"C",
|
| 504 |
+
"CB",
|
| 505 |
+
"O",
|
| 506 |
+
"CG",
|
| 507 |
+
"CG1",
|
| 508 |
+
"CG2",
|
| 509 |
+
"OG",
|
| 510 |
+
"OG1",
|
| 511 |
+
"SG",
|
| 512 |
+
"CD",
|
| 513 |
+
"CD1",
|
| 514 |
+
"CD2",
|
| 515 |
+
"ND1",
|
| 516 |
+
"ND2",
|
| 517 |
+
"OD1",
|
| 518 |
+
"OD2",
|
| 519 |
+
"SD",
|
| 520 |
+
"CE",
|
| 521 |
+
"CE1",
|
| 522 |
+
"CE2",
|
| 523 |
+
"CE3",
|
| 524 |
+
"NE",
|
| 525 |
+
"NE1",
|
| 526 |
+
"NE2",
|
| 527 |
+
"OE1",
|
| 528 |
+
"OE2",
|
| 529 |
+
"CH2",
|
| 530 |
+
"NH1",
|
| 531 |
+
"NH2",
|
| 532 |
+
"OH",
|
| 533 |
+
"CZ",
|
| 534 |
+
"CZ2",
|
| 535 |
+
"CZ3",
|
| 536 |
+
"NZ",
|
| 537 |
+
"OXT",
|
| 538 |
+
]
|
| 539 |
+
atom_order: dict[str, int] = {atom_type: i for i, atom_type in enumerate(atom_types)}
|
| 540 |
+
atom_type_num = len(atom_types) # := 37.
|
| 541 |
+
|
| 542 |
+
# A compact atom encoding with 14 columns
|
| 543 |
+
# pylint: disable=line-too-long
|
| 544 |
+
restype_name_to_atom14_names: dict[str, list[str]] = {
|
| 545 |
+
"ALA": ["N", "CA", "C", "O", "CB", "", "", "", "", "", "", "", "", ""],
|
| 546 |
+
"ARG": ["N", "CA", "C", "O", "CB", "CG", "CD", "NE", "CZ", "NH1", "NH2", "", "", ""],
|
| 547 |
+
"ASN": ["N", "CA", "C", "O", "CB", "CG", "OD1", "ND2", "", "", "", "", "", ""],
|
| 548 |
+
"ASP": ["N", "CA", "C", "O", "CB", "CG", "OD1", "OD2", "", "", "", "", "", ""],
|
| 549 |
+
"CYS": ["N", "CA", "C", "O", "CB", "SG", "", "", "", "", "", "", "", ""],
|
| 550 |
+
"GLN": ["N", "CA", "C", "O", "CB", "CG", "CD", "OE1", "NE2", "", "", "", "", ""],
|
| 551 |
+
"GLU": ["N", "CA", "C", "O", "CB", "CG", "CD", "OE1", "OE2", "", "", "", "", ""],
|
| 552 |
+
"GLY": ["N", "CA", "C", "O", "", "", "", "", "", "", "", "", "", ""],
|
| 553 |
+
"HIS": ["N", "CA", "C", "O", "CB", "CG", "ND1", "CD2", "CE1", "NE2", "", "", "", ""],
|
| 554 |
+
"ILE": ["N", "CA", "C", "O", "CB", "CG1", "CG2", "CD1", "", "", "", "", "", ""],
|
| 555 |
+
"LEU": ["N", "CA", "C", "O", "CB", "CG", "CD1", "CD2", "", "", "", "", "", ""],
|
| 556 |
+
"LYS": ["N", "CA", "C", "O", "CB", "CG", "CD", "CE", "NZ", "", "", "", "", ""],
|
| 557 |
+
"MET": ["N", "CA", "C", "O", "CB", "CG", "SD", "CE", "", "", "", "", "", ""],
|
| 558 |
+
"PHE": ["N", "CA", "C", "O", "CB", "CG", "CD1", "CD2", "CE1", "CE2", "CZ", "", "", ""],
|
| 559 |
+
"PRO": ["N", "CA", "C", "O", "CB", "CG", "CD", "", "", "", "", "", "", ""],
|
| 560 |
+
"SER": ["N", "CA", "C", "O", "CB", "OG", "", "", "", "", "", "", "", ""],
|
| 561 |
+
"THR": ["N", "CA", "C", "O", "CB", "OG1", "CG2", "", "", "", "", "", "", ""],
|
| 562 |
+
"TRP": ["N", "CA", "C", "O", "CB", "CG", "CD1", "CD2", "NE1", "CE2", "CE3", "CZ2", "CZ3", "CH2"],
|
| 563 |
+
"TYR": ["N", "CA", "C", "O", "CB", "CG", "CD1", "CD2", "CE1", "CE2", "CZ", "OH", "", ""],
|
| 564 |
+
"VAL": ["N", "CA", "C", "O", "CB", "CG1", "CG2", "", "", "", "", "", "", ""],
|
| 565 |
+
"UNK": ["", "", "", "", "", "", "", "", "", "", "", "", "", ""],
|
| 566 |
+
}
|
| 567 |
+
# pylint: enable=line-too-long
|
| 568 |
+
|
| 569 |
+
|
| 570 |
+
# This is the standard residue order when coding AA type as a number.
|
| 571 |
+
# Reproduce it by taking 3-letter AA codes and sorting them alphabetically.
|
| 572 |
+
restypes: list[str] = [
|
| 573 |
+
"A",
|
| 574 |
+
"R",
|
| 575 |
+
"N",
|
| 576 |
+
"D",
|
| 577 |
+
"C",
|
| 578 |
+
"Q",
|
| 579 |
+
"E",
|
| 580 |
+
"G",
|
| 581 |
+
"H",
|
| 582 |
+
"I",
|
| 583 |
+
"L",
|
| 584 |
+
"K",
|
| 585 |
+
"M",
|
| 586 |
+
"F",
|
| 587 |
+
"P",
|
| 588 |
+
"S",
|
| 589 |
+
"T",
|
| 590 |
+
"W",
|
| 591 |
+
"Y",
|
| 592 |
+
"V",
|
| 593 |
+
]
|
| 594 |
+
restype_order: dict[str, int] = {restype: i for i, restype in enumerate(restypes)}
|
| 595 |
+
restype_num = len(restypes) # := 20.
|
| 596 |
+
unk_restype_index = restype_num # Catch-all index for unknown restypes.
|
| 597 |
+
|
| 598 |
+
restypes_with_x: list[str] = restypes + ["X"]
|
| 599 |
+
restype_order_with_x: dict[str, int] = {restype: i for i, restype in enumerate(restypes_with_x)}
|
| 600 |
+
|
| 601 |
+
|
| 602 |
+
def sequence_to_onehot(sequence: str, mapping: Mapping[str, int], map_unknown_to_x: bool = False) -> np.ndarray:
|
| 603 |
+
"""Maps the given sequence into a one-hot encoded matrix.
|
| 604 |
+
|
| 605 |
+
Args:
|
| 606 |
+
sequence: An amino acid sequence.
|
| 607 |
+
mapping: A dictionary mapping amino acids to integers.
|
| 608 |
+
map_unknown_to_x: If True, any amino acid that is not in the mapping will be
|
| 609 |
+
mapped to the unknown amino acid 'X'. If the mapping doesn't contain amino acid 'X', an error will be thrown.
|
| 610 |
+
If False, any amino acid not in the mapping will throw an error.
|
| 611 |
+
|
| 612 |
+
Returns:
|
| 613 |
+
A numpy array of shape (seq_len, num_unique_aas) with one-hot encoding of the sequence.
|
| 614 |
+
|
| 615 |
+
Raises:
|
| 616 |
+
ValueError: If the mapping doesn't contain values from 0 to
|
| 617 |
+
num_unique_aas - 1 without any gaps.
|
| 618 |
+
"""
|
| 619 |
+
num_entries = max(mapping.values()) + 1
|
| 620 |
+
|
| 621 |
+
if sorted(set(mapping.values())) != list(range(num_entries)):
|
| 622 |
+
raise ValueError(
|
| 623 |
+
"The mapping must have values from 0 to num_unique_aas-1 without any gaps. Got: %s"
|
| 624 |
+
% sorted(mapping.values())
|
| 625 |
+
)
|
| 626 |
+
|
| 627 |
+
one_hot_arr = np.zeros((len(sequence), num_entries), dtype=np.int32)
|
| 628 |
+
|
| 629 |
+
for aa_index, aa_type in enumerate(sequence):
|
| 630 |
+
if map_unknown_to_x:
|
| 631 |
+
if aa_type.isalpha() and aa_type.isupper():
|
| 632 |
+
aa_id = mapping.get(aa_type, mapping["X"])
|
| 633 |
+
else:
|
| 634 |
+
raise ValueError(f"Invalid character in the sequence: {aa_type}")
|
| 635 |
+
else:
|
| 636 |
+
aa_id = mapping[aa_type]
|
| 637 |
+
one_hot_arr[aa_index, aa_id] = 1
|
| 638 |
+
|
| 639 |
+
return one_hot_arr
|
| 640 |
+
|
| 641 |
+
|
| 642 |
+
restype_1to3: dict[str, str] = {
|
| 643 |
+
"A": "ALA",
|
| 644 |
+
"R": "ARG",
|
| 645 |
+
"N": "ASN",
|
| 646 |
+
"D": "ASP",
|
| 647 |
+
"C": "CYS",
|
| 648 |
+
"Q": "GLN",
|
| 649 |
+
"E": "GLU",
|
| 650 |
+
"G": "GLY",
|
| 651 |
+
"H": "HIS",
|
| 652 |
+
"I": "ILE",
|
| 653 |
+
"L": "LEU",
|
| 654 |
+
"K": "LYS",
|
| 655 |
+
"M": "MET",
|
| 656 |
+
"F": "PHE",
|
| 657 |
+
"P": "PRO",
|
| 658 |
+
"S": "SER",
|
| 659 |
+
"T": "THR",
|
| 660 |
+
"W": "TRP",
|
| 661 |
+
"Y": "TYR",
|
| 662 |
+
"V": "VAL",
|
| 663 |
+
}
|
| 664 |
+
|
| 665 |
+
|
| 666 |
+
# NB: restype_3to1 differs from Bio.PDB.protein_letters_3to1 by being a simple
|
| 667 |
+
# 1-to-1 mapping of 3 letter names to one letter names. The latter contains
|
| 668 |
+
# many more, and less common, three letter names as keys and maps many of these
|
| 669 |
+
# to the same one letter name (including 'X' and 'U' which we don't use here).
|
| 670 |
+
restype_3to1: dict[str, str] = {v: k for k, v in restype_1to3.items()}
|
| 671 |
+
|
| 672 |
+
# Define a restype name for all unknown residues.
|
| 673 |
+
unk_restype = "UNK"
|
| 674 |
+
|
| 675 |
+
resnames: list[str] = [restype_1to3[r] for r in restypes] + [unk_restype]
|
| 676 |
+
resname_to_idx: dict[str, int] = {resname: i for i, resname in enumerate(resnames)}
|
| 677 |
+
|
| 678 |
+
|
| 679 |
+
# The mapping here uses hhblits convention, so that B is mapped to D, J and O
|
| 680 |
+
# are mapped to X, U is mapped to C, and Z is mapped to E. Other than that the
|
| 681 |
+
# remaining 20 amino acids are kept in alphabetical order.
|
| 682 |
+
# There are 2 non-amino acid codes, X (representing any amino acid) and
|
| 683 |
+
# "-" representing a missing amino acid in an alignment. The id for these
|
| 684 |
+
# codes is put at the end (20 and 21) so that they can easily be ignored if
|
| 685 |
+
# desired.
|
| 686 |
+
HHBLITS_AA_TO_ID: dict[str, int] = {
|
| 687 |
+
"A": 0,
|
| 688 |
+
"B": 2,
|
| 689 |
+
"C": 1,
|
| 690 |
+
"D": 2,
|
| 691 |
+
"E": 3,
|
| 692 |
+
"F": 4,
|
| 693 |
+
"G": 5,
|
| 694 |
+
"H": 6,
|
| 695 |
+
"I": 7,
|
| 696 |
+
"J": 20,
|
| 697 |
+
"K": 8,
|
| 698 |
+
"L": 9,
|
| 699 |
+
"M": 10,
|
| 700 |
+
"N": 11,
|
| 701 |
+
"O": 20,
|
| 702 |
+
"P": 12,
|
| 703 |
+
"Q": 13,
|
| 704 |
+
"R": 14,
|
| 705 |
+
"S": 15,
|
| 706 |
+
"T": 16,
|
| 707 |
+
"U": 1,
|
| 708 |
+
"V": 17,
|
| 709 |
+
"W": 18,
|
| 710 |
+
"X": 20,
|
| 711 |
+
"Y": 19,
|
| 712 |
+
"Z": 3,
|
| 713 |
+
"-": 21,
|
| 714 |
+
}
|
| 715 |
+
|
| 716 |
+
# Partial inversion of HHBLITS_AA_TO_ID.
|
| 717 |
+
ID_TO_HHBLITS_AA: dict[int, str] = {
|
| 718 |
+
0: "A",
|
| 719 |
+
1: "C", # Also U.
|
| 720 |
+
2: "D", # Also B.
|
| 721 |
+
3: "E", # Also Z.
|
| 722 |
+
4: "F",
|
| 723 |
+
5: "G",
|
| 724 |
+
6: "H",
|
| 725 |
+
7: "I",
|
| 726 |
+
8: "K",
|
| 727 |
+
9: "L",
|
| 728 |
+
10: "M",
|
| 729 |
+
11: "N",
|
| 730 |
+
12: "P",
|
| 731 |
+
13: "Q",
|
| 732 |
+
14: "R",
|
| 733 |
+
15: "S",
|
| 734 |
+
16: "T",
|
| 735 |
+
17: "V",
|
| 736 |
+
18: "W",
|
| 737 |
+
19: "Y",
|
| 738 |
+
20: "X", # Includes J and O.
|
| 739 |
+
21: "-",
|
| 740 |
+
}
|
| 741 |
+
|
| 742 |
+
restypes_with_x_and_gap: list[str] = restypes + ["X", "-"]
|
| 743 |
+
MAP_HHBLITS_AATYPE_TO_OUR_AATYPE: tuple[int, ...] = tuple(
|
| 744 |
+
restypes_with_x_and_gap.index(ID_TO_HHBLITS_AA[i]) for i in range(len(restypes_with_x_and_gap))
|
| 745 |
+
)
|
| 746 |
+
|
| 747 |
+
|
| 748 |
+
def _make_standard_atom_mask() -> np.ndarray:
|
| 749 |
+
"""Returns [num_res_types, num_atom_types] mask array."""
|
| 750 |
+
# +1 to account for unknown (all 0s).
|
| 751 |
+
mask = np.zeros([restype_num + 1, atom_type_num], dtype=np.int32)
|
| 752 |
+
for restype, restype_letter in enumerate(restypes):
|
| 753 |
+
restype_name = restype_1to3[restype_letter]
|
| 754 |
+
atom_names = residue_atoms[restype_name]
|
| 755 |
+
for atom_name in atom_names:
|
| 756 |
+
atom_type = atom_order[atom_name]
|
| 757 |
+
mask[restype, atom_type] = 1
|
| 758 |
+
return mask
|
| 759 |
+
|
| 760 |
+
|
| 761 |
+
STANDARD_ATOM_MASK = _make_standard_atom_mask()
|
| 762 |
+
|
| 763 |
+
|
| 764 |
+
# A one hot representation for the first and second atoms defining the axis
|
| 765 |
+
# of rotation for each chi-angle in each residue.
|
| 766 |
+
def chi_angle_atom(atom_index: int) -> np.ndarray:
|
| 767 |
+
"""Define chi-angle rigid groups via one-hot representations."""
|
| 768 |
+
chi_angles_index = {}
|
| 769 |
+
one_hots = []
|
| 770 |
+
|
| 771 |
+
for k, v in chi_angles_atoms.items():
|
| 772 |
+
indices = [atom_types.index(s[atom_index]) for s in v]
|
| 773 |
+
indices.extend([-1] * (4 - len(indices)))
|
| 774 |
+
chi_angles_index[k] = indices
|
| 775 |
+
|
| 776 |
+
for r in restypes:
|
| 777 |
+
res3 = restype_1to3[r]
|
| 778 |
+
one_hot = np.eye(atom_type_num)[chi_angles_index[res3]]
|
| 779 |
+
one_hots.append(one_hot)
|
| 780 |
+
|
| 781 |
+
one_hots.append(np.zeros([4, atom_type_num])) # Add zeros for residue `X`.
|
| 782 |
+
one_hot = np.stack(one_hots, axis=0)
|
| 783 |
+
one_hot = np.transpose(one_hot, [0, 2, 1])
|
| 784 |
+
|
| 785 |
+
return one_hot
|
| 786 |
+
|
| 787 |
+
|
| 788 |
+
chi_atom_1_one_hot = chi_angle_atom(1)
|
| 789 |
+
chi_atom_2_one_hot = chi_angle_atom(2)
|
| 790 |
+
|
| 791 |
+
# An array like chi_angles_atoms but using indices rather than names.
|
| 792 |
+
chi_angles_atom_indices_list: list[list[list[str]]] = [chi_angles_atoms[restype_1to3[r]] for r in restypes]
|
| 793 |
+
chi_angles_atom_indices_ours: list = map_structure_with_atom_order(chi_angles_atom_indices_list)
|
| 794 |
+
chi_angles_atom_indices = np.array(
|
| 795 |
+
[chi_atoms + ([[0, 0, 0, 0]] * (4 - len(chi_atoms))) for chi_atoms in chi_angles_atom_indices_list]
|
| 796 |
+
)
|
| 797 |
+
|
| 798 |
+
# Mapping from (res_name, atom_name) pairs to the atom's chi group index
|
| 799 |
+
# and atom index within that group.
|
| 800 |
+
chi_groups_for_atom: dict[tuple[str, str], list[tuple[int, int]]] = collections.defaultdict(list)
|
| 801 |
+
for res_name, chi_angle_atoms_for_res in chi_angles_atoms.items():
|
| 802 |
+
for chi_group_i, chi_group in enumerate(chi_angle_atoms_for_res):
|
| 803 |
+
for atom_i, atom in enumerate(chi_group):
|
| 804 |
+
chi_groups_for_atom[(res_name, atom)].append((chi_group_i, atom_i))
|
| 805 |
+
chi_groups_for_atom = dict(chi_groups_for_atom)
|
| 806 |
+
|
| 807 |
+
|
| 808 |
+
def _make_rigid_transformation_4x4(ex: np.ndarray, ey: np.ndarray, translation: np.ndarray) -> np.ndarray:
|
| 809 |
+
"""Create a rigid 4x4 transformation matrix from two axes and transl."""
|
| 810 |
+
# Normalize ex.
|
| 811 |
+
ex_normalized = ex / np.linalg.norm(ex)
|
| 812 |
+
|
| 813 |
+
# make ey perpendicular to ex
|
| 814 |
+
ey_normalized = ey - np.dot(ey, ex_normalized) * ex_normalized
|
| 815 |
+
ey_normalized /= np.linalg.norm(ey_normalized)
|
| 816 |
+
|
| 817 |
+
# compute ez as cross product
|
| 818 |
+
eznorm = np.cross(ex_normalized, ey_normalized)
|
| 819 |
+
m = np.stack([ex_normalized, ey_normalized, eznorm, translation]).transpose()
|
| 820 |
+
m = np.concatenate([m, [[0.0, 0.0, 0.0, 1.0]]], axis=0)
|
| 821 |
+
return m
|
| 822 |
+
|
| 823 |
+
|
| 824 |
+
# create an array with (restype, atomtype) --> rigid_group_idx
|
| 825 |
+
# and an array with (restype, atomtype, coord) for the atom positions
|
| 826 |
+
# and compute affine transformation matrices (4,4) from one rigid group to the
|
| 827 |
+
# previous group
|
| 828 |
+
restype_atom37_to_rigid_group = np.zeros([21, 37], dtype=int)
|
| 829 |
+
restype_atom37_mask = np.zeros([21, 37], dtype=np.float32)
|
| 830 |
+
restype_atom37_rigid_group_positions = np.zeros([21, 37, 3], dtype=np.float32)
|
| 831 |
+
restype_atom14_to_rigid_group = np.zeros([21, 14], dtype=int)
|
| 832 |
+
restype_atom14_mask = np.zeros([21, 14], dtype=np.float32)
|
| 833 |
+
restype_atom14_rigid_group_positions = np.zeros([21, 14, 3], dtype=np.float32)
|
| 834 |
+
restype_rigid_group_default_frame = np.zeros([21, 8, 4, 4], dtype=np.float32)
|
| 835 |
+
|
| 836 |
+
|
| 837 |
+
def _make_rigid_group_constants() -> None:
|
| 838 |
+
"""Fill the arrays above."""
|
| 839 |
+
for restype, restype_letter in enumerate(restypes):
|
| 840 |
+
resname = restype_1to3[restype_letter]
|
| 841 |
+
for atomname, group_idx, atom_position in rigid_group_atom_positions[resname]:
|
| 842 |
+
atomtype = atom_order[atomname]
|
| 843 |
+
restype_atom37_to_rigid_group[restype, atomtype] = group_idx
|
| 844 |
+
restype_atom37_mask[restype, atomtype] = 1
|
| 845 |
+
restype_atom37_rigid_group_positions[restype, atomtype, :] = atom_position
|
| 846 |
+
|
| 847 |
+
atom14idx = restype_name_to_atom14_names[resname].index(atomname)
|
| 848 |
+
restype_atom14_to_rigid_group[restype, atom14idx] = group_idx
|
| 849 |
+
restype_atom14_mask[restype, atom14idx] = 1
|
| 850 |
+
restype_atom14_rigid_group_positions[restype, atom14idx, :] = atom_position
|
| 851 |
+
|
| 852 |
+
for restype, restype_letter in enumerate(restypes):
|
| 853 |
+
resname = restype_1to3[restype_letter]
|
| 854 |
+
atom_positions: dict[str, np.ndarray] = {
|
| 855 |
+
name: np.array(pos) for name, _, pos in rigid_group_atom_positions[resname]
|
| 856 |
+
}
|
| 857 |
+
|
| 858 |
+
# backbone to backbone is the identity transform
|
| 859 |
+
restype_rigid_group_default_frame[restype, 0, :, :] = np.eye(4)
|
| 860 |
+
|
| 861 |
+
# pre-omega-frame to backbone (currently dummy identity matrix)
|
| 862 |
+
restype_rigid_group_default_frame[restype, 1, :, :] = np.eye(4)
|
| 863 |
+
|
| 864 |
+
# phi-frame to backbone
|
| 865 |
+
mat = _make_rigid_transformation_4x4(
|
| 866 |
+
ex=atom_positions["N"] - atom_positions["CA"],
|
| 867 |
+
ey=np.array([1.0, 0.0, 0.0]),
|
| 868 |
+
translation=atom_positions["N"],
|
| 869 |
+
)
|
| 870 |
+
restype_rigid_group_default_frame[restype, 2, :, :] = mat
|
| 871 |
+
|
| 872 |
+
# psi-frame to backbone
|
| 873 |
+
mat = _make_rigid_transformation_4x4(
|
| 874 |
+
ex=atom_positions["C"] - atom_positions["CA"],
|
| 875 |
+
ey=atom_positions["CA"] - atom_positions["N"],
|
| 876 |
+
translation=atom_positions["C"],
|
| 877 |
+
)
|
| 878 |
+
restype_rigid_group_default_frame[restype, 3, :, :] = mat
|
| 879 |
+
|
| 880 |
+
# chi1-frame to backbone
|
| 881 |
+
if chi_angles_mask[restype][0]:
|
| 882 |
+
base_atom_names = chi_angles_atoms[resname][0]
|
| 883 |
+
base_atom_positions = [atom_positions[name] for name in base_atom_names]
|
| 884 |
+
mat = _make_rigid_transformation_4x4(
|
| 885 |
+
ex=base_atom_positions[2] - base_atom_positions[1],
|
| 886 |
+
ey=base_atom_positions[0] - base_atom_positions[1],
|
| 887 |
+
translation=base_atom_positions[2],
|
| 888 |
+
)
|
| 889 |
+
restype_rigid_group_default_frame[restype, 4, :, :] = mat
|
| 890 |
+
|
| 891 |
+
# chi2-frame to chi1-frame
|
| 892 |
+
# chi3-frame to chi2-frame
|
| 893 |
+
# chi4-frame to chi3-frame
|
| 894 |
+
# luckily all rotation axes for the next frame start at (0,0,0) of the
|
| 895 |
+
# previous frame
|
| 896 |
+
for chi_idx in range(1, 4):
|
| 897 |
+
if chi_angles_mask[restype][chi_idx]:
|
| 898 |
+
axis_end_atom_name = chi_angles_atoms[resname][chi_idx][2]
|
| 899 |
+
axis_end_atom_position = atom_positions[axis_end_atom_name]
|
| 900 |
+
mat = _make_rigid_transformation_4x4(
|
| 901 |
+
ex=axis_end_atom_position,
|
| 902 |
+
ey=np.array([-1.0, 0.0, 0.0]),
|
| 903 |
+
translation=axis_end_atom_position,
|
| 904 |
+
)
|
| 905 |
+
restype_rigid_group_default_frame[restype, 4 + chi_idx, :, :] = mat
|
| 906 |
+
|
| 907 |
+
|
| 908 |
+
_make_rigid_group_constants()
|
| 909 |
+
|
| 910 |
+
|
| 911 |
+
def make_atom14_dists_bounds(
|
| 912 |
+
overlap_tolerance: float = 1.5,
|
| 913 |
+
bond_length_tolerance_factor: int = 15,
|
| 914 |
+
) -> dict[str, np.ndarray]:
|
| 915 |
+
"""compute upper and lower bounds for bonds to assess violations."""
|
| 916 |
+
restype_atom14_bond_lower_bound = np.zeros([21, 14, 14], np.float32)
|
| 917 |
+
restype_atom14_bond_upper_bound = np.zeros([21, 14, 14], np.float32)
|
| 918 |
+
restype_atom14_bond_stddev = np.zeros([21, 14, 14], np.float32)
|
| 919 |
+
residue_bonds, residue_virtual_bonds, _ = load_stereo_chemical_props()
|
| 920 |
+
for restype, restype_letter in enumerate(restypes):
|
| 921 |
+
resname = restype_1to3[restype_letter]
|
| 922 |
+
atom_list = restype_name_to_atom14_names[resname]
|
| 923 |
+
|
| 924 |
+
# create lower and upper bounds for clashes
|
| 925 |
+
for atom1_idx, atom1_name in enumerate(atom_list):
|
| 926 |
+
if not atom1_name:
|
| 927 |
+
continue
|
| 928 |
+
atom1_radius = van_der_waals_radius[atom1_name[0]]
|
| 929 |
+
for atom2_idx, atom2_name in enumerate(atom_list):
|
| 930 |
+
if (not atom2_name) or atom1_idx == atom2_idx:
|
| 931 |
+
continue
|
| 932 |
+
atom2_radius = van_der_waals_radius[atom2_name[0]]
|
| 933 |
+
lower = atom1_radius + atom2_radius - overlap_tolerance
|
| 934 |
+
upper = 1e10
|
| 935 |
+
restype_atom14_bond_lower_bound[restype, atom1_idx, atom2_idx] = lower
|
| 936 |
+
restype_atom14_bond_lower_bound[restype, atom2_idx, atom1_idx] = lower
|
| 937 |
+
restype_atom14_bond_upper_bound[restype, atom1_idx, atom2_idx] = upper
|
| 938 |
+
restype_atom14_bond_upper_bound[restype, atom2_idx, atom1_idx] = upper
|
| 939 |
+
|
| 940 |
+
# overwrite lower and upper bounds for bonds and angles
|
| 941 |
+
for b in residue_bonds[resname] + residue_virtual_bonds[resname]:
|
| 942 |
+
atom1_idx = atom_list.index(b.atom1_name)
|
| 943 |
+
atom2_idx = atom_list.index(b.atom2_name)
|
| 944 |
+
lower = b.length - bond_length_tolerance_factor * b.stddev
|
| 945 |
+
upper = b.length + bond_length_tolerance_factor * b.stddev
|
| 946 |
+
restype_atom14_bond_lower_bound[restype, atom1_idx, atom2_idx] = lower
|
| 947 |
+
restype_atom14_bond_lower_bound[restype, atom2_idx, atom1_idx] = lower
|
| 948 |
+
restype_atom14_bond_upper_bound[restype, atom1_idx, atom2_idx] = upper
|
| 949 |
+
restype_atom14_bond_upper_bound[restype, atom2_idx, atom1_idx] = upper
|
| 950 |
+
restype_atom14_bond_stddev[restype, atom1_idx, atom2_idx] = b.stddev
|
| 951 |
+
restype_atom14_bond_stddev[restype, atom2_idx, atom1_idx] = b.stddev
|
| 952 |
+
return {
|
| 953 |
+
"lower_bound": restype_atom14_bond_lower_bound, # shape (21,14,14)
|
| 954 |
+
"upper_bound": restype_atom14_bond_upper_bound, # shape (21,14,14)
|
| 955 |
+
"stddev": restype_atom14_bond_stddev, # shape (21,14,14)
|
| 956 |
+
}
|
| 957 |
+
|
| 958 |
+
|
| 959 |
+
restype_atom14_ambiguous_atoms = np.zeros((21, 14), dtype=np.float32)
|
| 960 |
+
restype_atom14_ambiguous_atoms_swap_idx: np.ndarray = np.tile(np.arange(14, dtype=int), (21, 1))
|
| 961 |
+
|
| 962 |
+
|
| 963 |
+
def _make_atom14_ambiguity_feats() -> None:
|
| 964 |
+
for res, pairs in residue_atom_renaming_swaps.items():
|
| 965 |
+
res_idx = restype_order[restype_3to1[res]]
|
| 966 |
+
for atom1, atom2 in pairs.items():
|
| 967 |
+
atom1_idx = restype_name_to_atom14_names[res].index(atom1)
|
| 968 |
+
atom2_idx = restype_name_to_atom14_names[res].index(atom2)
|
| 969 |
+
restype_atom14_ambiguous_atoms[res_idx, atom1_idx] = 1
|
| 970 |
+
restype_atom14_ambiguous_atoms[res_idx, atom2_idx] = 1
|
| 971 |
+
restype_atom14_ambiguous_atoms_swap_idx[res_idx, atom1_idx] = atom2_idx
|
| 972 |
+
restype_atom14_ambiguous_atoms_swap_idx[res_idx, atom2_idx] = atom1_idx
|
| 973 |
+
|
| 974 |
+
|
| 975 |
+
_make_atom14_ambiguity_feats()
|
| 976 |
+
|
| 977 |
+
|
| 978 |
+
def aatype_to_str_sequence(aatype: Sequence[int]) -> str:
|
| 979 |
+
return "".join([restypes_with_x[aatype[i]] for i in range(len(aatype))])
|
LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/esm/openfold_utils/rigid_utils.py
ADDED
|
@@ -0,0 +1,1243 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2021 AlQuraishi Laboratory
|
| 2 |
+
# Copyright 2021 DeepMind Technologies Limited
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
from collections.abc import Callable, Sequence
|
| 19 |
+
from functools import cache
|
| 20 |
+
from typing import Any
|
| 21 |
+
|
| 22 |
+
import numpy as np
|
| 23 |
+
import torch
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def rot_matmul(a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
|
| 27 |
+
"""
|
| 28 |
+
Performs matrix multiplication of two rotation matrix tensors. Written out by hand to avoid AMP downcasting.
|
| 29 |
+
|
| 30 |
+
Args:
|
| 31 |
+
a: [*, 3, 3] left multiplicand
|
| 32 |
+
b: [*, 3, 3] right multiplicand
|
| 33 |
+
Returns:
|
| 34 |
+
The product ab
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
def row_mul(i: int) -> torch.Tensor:
|
| 38 |
+
return torch.stack(
|
| 39 |
+
[
|
| 40 |
+
a[..., i, 0] * b[..., 0, 0] + a[..., i, 1] * b[..., 1, 0] + a[..., i, 2] * b[..., 2, 0],
|
| 41 |
+
a[..., i, 0] * b[..., 0, 1] + a[..., i, 1] * b[..., 1, 1] + a[..., i, 2] * b[..., 2, 1],
|
| 42 |
+
a[..., i, 0] * b[..., 0, 2] + a[..., i, 1] * b[..., 1, 2] + a[..., i, 2] * b[..., 2, 2],
|
| 43 |
+
],
|
| 44 |
+
dim=-1,
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
return torch.stack(
|
| 48 |
+
[
|
| 49 |
+
row_mul(0),
|
| 50 |
+
row_mul(1),
|
| 51 |
+
row_mul(2),
|
| 52 |
+
],
|
| 53 |
+
dim=-2,
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def rot_vec_mul(r: torch.Tensor, t: torch.Tensor) -> torch.Tensor:
|
| 58 |
+
"""
|
| 59 |
+
Applies a rotation to a vector. Written out by hand to avoid transfer to avoid AMP downcasting.
|
| 60 |
+
|
| 61 |
+
Args:
|
| 62 |
+
r: [*, 3, 3] rotation matrices
|
| 63 |
+
t: [*, 3] coordinate tensors
|
| 64 |
+
Returns:
|
| 65 |
+
[*, 3] rotated coordinates
|
| 66 |
+
"""
|
| 67 |
+
x, y, z = torch.unbind(t, dim=-1)
|
| 68 |
+
return torch.stack(
|
| 69 |
+
[
|
| 70 |
+
r[..., 0, 0] * x + r[..., 0, 1] * y + r[..., 0, 2] * z,
|
| 71 |
+
r[..., 1, 0] * x + r[..., 1, 1] * y + r[..., 1, 2] * z,
|
| 72 |
+
r[..., 2, 0] * x + r[..., 2, 1] * y + r[..., 2, 2] * z,
|
| 73 |
+
],
|
| 74 |
+
dim=-1,
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
@cache
|
| 79 |
+
def identity_rot_mats(
|
| 80 |
+
batch_dims: tuple[int, ...],
|
| 81 |
+
dtype: torch.dtype | None = None,
|
| 82 |
+
device: torch.device | None = None,
|
| 83 |
+
requires_grad: bool = True,
|
| 84 |
+
) -> torch.Tensor:
|
| 85 |
+
rots = torch.eye(3, dtype=dtype, device=device, requires_grad=requires_grad)
|
| 86 |
+
rots = rots.view(*((1,) * len(batch_dims)), 3, 3)
|
| 87 |
+
rots = rots.expand(*batch_dims, -1, -1)
|
| 88 |
+
rots = rots.contiguous()
|
| 89 |
+
|
| 90 |
+
return rots
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
@cache
|
| 94 |
+
def identity_trans(
|
| 95 |
+
batch_dims: tuple[int, ...],
|
| 96 |
+
dtype: torch.dtype | None = None,
|
| 97 |
+
device: torch.device | None = None,
|
| 98 |
+
requires_grad: bool = True,
|
| 99 |
+
) -> torch.Tensor:
|
| 100 |
+
trans = torch.zeros((*batch_dims, 3), dtype=dtype, device=device, requires_grad=requires_grad)
|
| 101 |
+
return trans
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
@cache
|
| 105 |
+
def identity_quats(
|
| 106 |
+
batch_dims: tuple[int, ...],
|
| 107 |
+
dtype: torch.dtype | None = None,
|
| 108 |
+
device: torch.device | None = None,
|
| 109 |
+
requires_grad: bool = True,
|
| 110 |
+
) -> torch.Tensor:
|
| 111 |
+
quat = torch.zeros((*batch_dims, 4), dtype=dtype, device=device, requires_grad=requires_grad)
|
| 112 |
+
|
| 113 |
+
with torch.no_grad():
|
| 114 |
+
quat[..., 0] = 1
|
| 115 |
+
|
| 116 |
+
return quat
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
_quat_elements: list[str] = ["a", "b", "c", "d"]
|
| 120 |
+
_qtr_keys: list[str] = [l1 + l2 for l1 in _quat_elements for l2 in _quat_elements]
|
| 121 |
+
_qtr_ind_dict: dict[str, int] = {key: ind for ind, key in enumerate(_qtr_keys)}
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def _to_mat(pairs: list[tuple[str, int]]) -> np.ndarray:
|
| 125 |
+
mat = np.zeros((4, 4))
|
| 126 |
+
for key, value in pairs:
|
| 127 |
+
ind = _qtr_ind_dict[key]
|
| 128 |
+
mat[ind // 4][ind % 4] = value
|
| 129 |
+
|
| 130 |
+
return mat
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
_QTR_MAT = np.zeros((4, 4, 3, 3))
|
| 134 |
+
_QTR_MAT[..., 0, 0] = _to_mat([("aa", 1), ("bb", 1), ("cc", -1), ("dd", -1)])
|
| 135 |
+
_QTR_MAT[..., 0, 1] = _to_mat([("bc", 2), ("ad", -2)])
|
| 136 |
+
_QTR_MAT[..., 0, 2] = _to_mat([("bd", 2), ("ac", 2)])
|
| 137 |
+
_QTR_MAT[..., 1, 0] = _to_mat([("bc", 2), ("ad", 2)])
|
| 138 |
+
_QTR_MAT[..., 1, 1] = _to_mat([("aa", 1), ("bb", -1), ("cc", 1), ("dd", -1)])
|
| 139 |
+
_QTR_MAT[..., 1, 2] = _to_mat([("cd", 2), ("ab", -2)])
|
| 140 |
+
_QTR_MAT[..., 2, 0] = _to_mat([("bd", 2), ("ac", -2)])
|
| 141 |
+
_QTR_MAT[..., 2, 1] = _to_mat([("cd", 2), ("ab", 2)])
|
| 142 |
+
_QTR_MAT[..., 2, 2] = _to_mat([("aa", 1), ("bb", -1), ("cc", -1), ("dd", 1)])
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def quat_to_rot(quat: torch.Tensor) -> torch.Tensor:
|
| 146 |
+
"""
|
| 147 |
+
Converts a quaternion to a rotation matrix.
|
| 148 |
+
|
| 149 |
+
Args:
|
| 150 |
+
quat: [*, 4] quaternions
|
| 151 |
+
Returns:
|
| 152 |
+
[*, 3, 3] rotation matrices
|
| 153 |
+
"""
|
| 154 |
+
# [*, 4, 4]
|
| 155 |
+
quat = quat[..., None] * quat[..., None, :]
|
| 156 |
+
|
| 157 |
+
# [4, 4, 3, 3]
|
| 158 |
+
mat = _get_quat("_QTR_MAT", dtype=quat.dtype, device=quat.device)
|
| 159 |
+
|
| 160 |
+
# [*, 4, 4, 3, 3]
|
| 161 |
+
shaped_qtr_mat = mat.view((1,) * len(quat.shape[:-2]) + mat.shape)
|
| 162 |
+
quat = quat[..., None, None] * shaped_qtr_mat
|
| 163 |
+
|
| 164 |
+
# [*, 3, 3]
|
| 165 |
+
return torch.sum(quat, dim=(-3, -4))
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def rot_to_quat(rot: torch.Tensor) -> torch.Tensor:
|
| 169 |
+
if rot.shape[-2:] != (3, 3):
|
| 170 |
+
raise ValueError("Input rotation is incorrectly shaped")
|
| 171 |
+
|
| 172 |
+
[[xx, xy, xz], [yx, yy, yz], [zx, zy, zz]] = [[rot[..., i, j] for j in range(3)] for i in range(3)]
|
| 173 |
+
|
| 174 |
+
k = [
|
| 175 |
+
[
|
| 176 |
+
xx + yy + zz,
|
| 177 |
+
zy - yz,
|
| 178 |
+
xz - zx,
|
| 179 |
+
yx - xy,
|
| 180 |
+
],
|
| 181 |
+
[
|
| 182 |
+
zy - yz,
|
| 183 |
+
xx - yy - zz,
|
| 184 |
+
xy + yx,
|
| 185 |
+
xz + zx,
|
| 186 |
+
],
|
| 187 |
+
[
|
| 188 |
+
xz - zx,
|
| 189 |
+
xy + yx,
|
| 190 |
+
yy - xx - zz,
|
| 191 |
+
yz + zy,
|
| 192 |
+
],
|
| 193 |
+
[
|
| 194 |
+
yx - xy,
|
| 195 |
+
xz + zx,
|
| 196 |
+
yz + zy,
|
| 197 |
+
zz - xx - yy,
|
| 198 |
+
],
|
| 199 |
+
]
|
| 200 |
+
|
| 201 |
+
_, vectors = torch.linalg.eigh((1.0 / 3.0) * torch.stack([torch.stack(t, dim=-1) for t in k], dim=-2))
|
| 202 |
+
return vectors[..., -1]
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
_QUAT_MULTIPLY = np.zeros((4, 4, 4))
|
| 206 |
+
_QUAT_MULTIPLY[:, :, 0] = [[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, -1]]
|
| 207 |
+
|
| 208 |
+
_QUAT_MULTIPLY[:, :, 1] = [[0, 1, 0, 0], [1, 0, 0, 0], [0, 0, 0, 1], [0, 0, -1, 0]]
|
| 209 |
+
|
| 210 |
+
_QUAT_MULTIPLY[:, :, 2] = [[0, 0, 1, 0], [0, 0, 0, -1], [1, 0, 0, 0], [0, 1, 0, 0]]
|
| 211 |
+
|
| 212 |
+
_QUAT_MULTIPLY[:, :, 3] = [[0, 0, 0, 1], [0, 0, 1, 0], [0, -1, 0, 0], [1, 0, 0, 0]]
|
| 213 |
+
|
| 214 |
+
_QUAT_MULTIPLY_BY_VEC = _QUAT_MULTIPLY[:, 1:, :]
|
| 215 |
+
|
| 216 |
+
_CACHED_QUATS: dict[str, np.ndarray] = {
|
| 217 |
+
"_QTR_MAT": _QTR_MAT,
|
| 218 |
+
"_QUAT_MULTIPLY": _QUAT_MULTIPLY,
|
| 219 |
+
"_QUAT_MULTIPLY_BY_VEC": _QUAT_MULTIPLY_BY_VEC,
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
@cache
|
| 224 |
+
def _get_quat(quat_key: str, dtype: torch.dtype, device: torch.device) -> torch.Tensor:
|
| 225 |
+
return torch.tensor(_CACHED_QUATS[quat_key], dtype=dtype, device=device)
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
def quat_multiply(quat1: torch.Tensor, quat2: torch.Tensor) -> torch.Tensor:
|
| 229 |
+
"""Multiply a quaternion by another quaternion."""
|
| 230 |
+
mat = _get_quat("_QUAT_MULTIPLY", dtype=quat1.dtype, device=quat1.device)
|
| 231 |
+
reshaped_mat = mat.view((1,) * len(quat1.shape[:-1]) + mat.shape)
|
| 232 |
+
return torch.sum(reshaped_mat * quat1[..., :, None, None] * quat2[..., None, :, None], dim=(-3, -2))
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
def quat_multiply_by_vec(quat: torch.Tensor, vec: torch.Tensor) -> torch.Tensor:
|
| 236 |
+
"""Multiply a quaternion by a pure-vector quaternion."""
|
| 237 |
+
mat = _get_quat("_QUAT_MULTIPLY_BY_VEC", dtype=quat.dtype, device=quat.device)
|
| 238 |
+
reshaped_mat = mat.view((1,) * len(quat.shape[:-1]) + mat.shape)
|
| 239 |
+
return torch.sum(reshaped_mat * quat[..., :, None, None] * vec[..., None, :, None], dim=(-3, -2))
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
def invert_rot_mat(rot_mat: torch.Tensor) -> torch.Tensor:
|
| 243 |
+
return rot_mat.transpose(-1, -2)
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
def invert_quat(quat: torch.Tensor) -> torch.Tensor:
|
| 247 |
+
quat_prime = quat.clone()
|
| 248 |
+
quat_prime[..., 1:] *= -1
|
| 249 |
+
inv = quat_prime / torch.sum(quat**2, dim=-1, keepdim=True)
|
| 250 |
+
return inv
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
class Rotation:
|
| 254 |
+
"""
|
| 255 |
+
A 3D rotation. Depending on how the object is initialized, the rotation is represented by either a rotation matrix
|
| 256 |
+
or a quaternion, though both formats are made available by helper functions. To simplify gradient computation, the
|
| 257 |
+
underlying format of the rotation cannot be changed in-place. Like Rigid, the class is designed to mimic the
|
| 258 |
+
behavior of a torch Tensor, almost as if each Rotation object were a tensor of rotations, in one format or another.
|
| 259 |
+
"""
|
| 260 |
+
|
| 261 |
+
def __init__(
|
| 262 |
+
self,
|
| 263 |
+
rot_mats: torch.Tensor | None = None,
|
| 264 |
+
quats: torch.Tensor | None = None,
|
| 265 |
+
normalize_quats: bool = True,
|
| 266 |
+
):
|
| 267 |
+
"""
|
| 268 |
+
Args:
|
| 269 |
+
rot_mats:
|
| 270 |
+
A [*, 3, 3] rotation matrix tensor. Mutually exclusive with quats
|
| 271 |
+
quats:
|
| 272 |
+
A [*, 4] quaternion. Mutually exclusive with rot_mats. If normalize_quats is not True, must be a unit
|
| 273 |
+
quaternion
|
| 274 |
+
normalize_quats:
|
| 275 |
+
If quats is specified, whether to normalize quats
|
| 276 |
+
"""
|
| 277 |
+
if (rot_mats is None and quats is None) or (rot_mats is not None and quats is not None):
|
| 278 |
+
raise ValueError("Exactly one input argument must be specified")
|
| 279 |
+
|
| 280 |
+
if (rot_mats is not None and rot_mats.shape[-2:] != (3, 3)) or (quats is not None and quats.shape[-1] != 4):
|
| 281 |
+
raise ValueError("Incorrectly shaped rotation matrix or quaternion")
|
| 282 |
+
|
| 283 |
+
# Force full-precision
|
| 284 |
+
if quats is not None:
|
| 285 |
+
quats = quats.to(dtype=torch.float32)
|
| 286 |
+
if rot_mats is not None:
|
| 287 |
+
rot_mats = rot_mats.to(dtype=torch.float32)
|
| 288 |
+
|
| 289 |
+
if quats is not None and normalize_quats:
|
| 290 |
+
quats = quats / torch.linalg.norm(quats, dim=-1, keepdim=True)
|
| 291 |
+
|
| 292 |
+
self._rot_mats = rot_mats
|
| 293 |
+
self._quats = quats
|
| 294 |
+
|
| 295 |
+
@staticmethod
|
| 296 |
+
def identity(
|
| 297 |
+
shape,
|
| 298 |
+
dtype: torch.dtype | None = None,
|
| 299 |
+
device: torch.device | None = None,
|
| 300 |
+
requires_grad: bool = True,
|
| 301 |
+
fmt: str = "quat",
|
| 302 |
+
) -> Rotation:
|
| 303 |
+
"""
|
| 304 |
+
Returns an identity Rotation.
|
| 305 |
+
|
| 306 |
+
Args:
|
| 307 |
+
shape:
|
| 308 |
+
The "shape" of the resulting Rotation object. See documentation for the shape property
|
| 309 |
+
dtype:
|
| 310 |
+
The torch dtype for the rotation
|
| 311 |
+
device:
|
| 312 |
+
The torch device for the new rotation
|
| 313 |
+
requires_grad:
|
| 314 |
+
Whether the underlying tensors in the new rotation object should require gradient computation
|
| 315 |
+
fmt:
|
| 316 |
+
One of "quat" or "rot_mat". Determines the underlying format of the new object's rotation
|
| 317 |
+
Returns:
|
| 318 |
+
A new identity rotation
|
| 319 |
+
"""
|
| 320 |
+
if fmt == "rot_mat":
|
| 321 |
+
rot_mats = identity_rot_mats(
|
| 322 |
+
shape,
|
| 323 |
+
dtype,
|
| 324 |
+
device,
|
| 325 |
+
requires_grad,
|
| 326 |
+
)
|
| 327 |
+
return Rotation(rot_mats=rot_mats, quats=None)
|
| 328 |
+
elif fmt == "quat":
|
| 329 |
+
quats = identity_quats(shape, dtype, device, requires_grad)
|
| 330 |
+
return Rotation(rot_mats=None, quats=quats, normalize_quats=False)
|
| 331 |
+
else:
|
| 332 |
+
raise ValueError(f"Invalid format: f{fmt}")
|
| 333 |
+
|
| 334 |
+
# Magic methods
|
| 335 |
+
|
| 336 |
+
def __getitem__(self, index: Any) -> Rotation:
|
| 337 |
+
"""
|
| 338 |
+
Allows torch-style indexing over the virtual shape of the rotation object. See documentation for the shape
|
| 339 |
+
property.
|
| 340 |
+
|
| 341 |
+
Args:
|
| 342 |
+
index:
|
| 343 |
+
A torch index. E.g. (1, 3, 2), or (slice(None,))
|
| 344 |
+
Returns:
|
| 345 |
+
The indexed rotation
|
| 346 |
+
"""
|
| 347 |
+
if type(index) is not tuple:
|
| 348 |
+
index = (index,)
|
| 349 |
+
|
| 350 |
+
if self._rot_mats is not None:
|
| 351 |
+
rot_mats = self._rot_mats[index + (slice(None), slice(None))]
|
| 352 |
+
return Rotation(rot_mats=rot_mats)
|
| 353 |
+
elif self._quats is not None:
|
| 354 |
+
quats = self._quats[index + (slice(None),)]
|
| 355 |
+
return Rotation(quats=quats, normalize_quats=False)
|
| 356 |
+
else:
|
| 357 |
+
raise ValueError("Both rotations are None")
|
| 358 |
+
|
| 359 |
+
def __mul__(self, right: torch.Tensor) -> Rotation:
|
| 360 |
+
"""
|
| 361 |
+
Pointwise left multiplication of the rotation with a tensor. Can be used to e.g. mask the Rotation.
|
| 362 |
+
|
| 363 |
+
Args:
|
| 364 |
+
right:
|
| 365 |
+
The tensor multiplicand
|
| 366 |
+
Returns:
|
| 367 |
+
The product
|
| 368 |
+
"""
|
| 369 |
+
if not (isinstance(right, torch.Tensor)):
|
| 370 |
+
raise TypeError("The other multiplicand must be a Tensor")
|
| 371 |
+
|
| 372 |
+
if self._rot_mats is not None:
|
| 373 |
+
rot_mats = self._rot_mats * right[..., None, None]
|
| 374 |
+
return Rotation(rot_mats=rot_mats, quats=None)
|
| 375 |
+
elif self._quats is not None:
|
| 376 |
+
quats = self._quats * right[..., None]
|
| 377 |
+
return Rotation(rot_mats=None, quats=quats, normalize_quats=False)
|
| 378 |
+
else:
|
| 379 |
+
raise ValueError("Both rotations are None")
|
| 380 |
+
|
| 381 |
+
def __rmul__(self, left: torch.Tensor) -> Rotation:
|
| 382 |
+
"""
|
| 383 |
+
Reverse pointwise multiplication of the rotation with a tensor.
|
| 384 |
+
|
| 385 |
+
Args:
|
| 386 |
+
left:
|
| 387 |
+
The left multiplicand
|
| 388 |
+
Returns:
|
| 389 |
+
The product
|
| 390 |
+
"""
|
| 391 |
+
return self.__mul__(left)
|
| 392 |
+
|
| 393 |
+
# Properties
|
| 394 |
+
|
| 395 |
+
@property
|
| 396 |
+
def shape(self) -> torch.Size:
|
| 397 |
+
"""
|
| 398 |
+
Returns the virtual shape of the rotation object. This shape is defined as the batch dimensions of the
|
| 399 |
+
underlying rotation matrix or quaternion. If the Rotation was initialized with a [10, 3, 3] rotation matrix
|
| 400 |
+
tensor, for example, the resulting shape would be [10].
|
| 401 |
+
|
| 402 |
+
Returns:
|
| 403 |
+
The virtual shape of the rotation object
|
| 404 |
+
"""
|
| 405 |
+
if self._rot_mats is not None:
|
| 406 |
+
return self._rot_mats.shape[:-2]
|
| 407 |
+
elif self._quats is not None:
|
| 408 |
+
return self._quats.shape[:-1]
|
| 409 |
+
else:
|
| 410 |
+
raise ValueError("Both rotations are None")
|
| 411 |
+
|
| 412 |
+
@property
|
| 413 |
+
def dtype(self) -> torch.dtype:
|
| 414 |
+
"""
|
| 415 |
+
Returns the dtype of the underlying rotation.
|
| 416 |
+
|
| 417 |
+
Returns:
|
| 418 |
+
The dtype of the underlying rotation
|
| 419 |
+
"""
|
| 420 |
+
if self._rot_mats is not None:
|
| 421 |
+
return self._rot_mats.dtype
|
| 422 |
+
elif self._quats is not None:
|
| 423 |
+
return self._quats.dtype
|
| 424 |
+
else:
|
| 425 |
+
raise ValueError("Both rotations are None")
|
| 426 |
+
|
| 427 |
+
@property
|
| 428 |
+
def device(self) -> torch.device:
|
| 429 |
+
"""
|
| 430 |
+
The device of the underlying rotation
|
| 431 |
+
|
| 432 |
+
Returns:
|
| 433 |
+
The device of the underlying rotation
|
| 434 |
+
"""
|
| 435 |
+
if self._rot_mats is not None:
|
| 436 |
+
return self._rot_mats.device
|
| 437 |
+
elif self._quats is not None:
|
| 438 |
+
return self._quats.device
|
| 439 |
+
else:
|
| 440 |
+
raise ValueError("Both rotations are None")
|
| 441 |
+
|
| 442 |
+
@property
|
| 443 |
+
def requires_grad(self) -> bool:
|
| 444 |
+
"""
|
| 445 |
+
Returns the requires_grad property of the underlying rotation
|
| 446 |
+
|
| 447 |
+
Returns:
|
| 448 |
+
The requires_grad property of the underlying tensor
|
| 449 |
+
"""
|
| 450 |
+
if self._rot_mats is not None:
|
| 451 |
+
return self._rot_mats.requires_grad
|
| 452 |
+
elif self._quats is not None:
|
| 453 |
+
return self._quats.requires_grad
|
| 454 |
+
else:
|
| 455 |
+
raise ValueError("Both rotations are None")
|
| 456 |
+
|
| 457 |
+
def get_rot_mats(self) -> torch.Tensor:
|
| 458 |
+
"""
|
| 459 |
+
Returns the underlying rotation as a rotation matrix tensor.
|
| 460 |
+
|
| 461 |
+
Returns:
|
| 462 |
+
The rotation as a rotation matrix tensor
|
| 463 |
+
"""
|
| 464 |
+
if self._rot_mats is not None:
|
| 465 |
+
return self._rot_mats
|
| 466 |
+
elif self._quats is not None:
|
| 467 |
+
return quat_to_rot(self._quats)
|
| 468 |
+
else:
|
| 469 |
+
raise ValueError("Both rotations are None")
|
| 470 |
+
|
| 471 |
+
def get_quats(self) -> torch.Tensor:
|
| 472 |
+
"""
|
| 473 |
+
Returns the underlying rotation as a quaternion tensor.
|
| 474 |
+
|
| 475 |
+
Depending on whether the Rotation was initialized with a quaternion, this function may call torch.linalg.eigh.
|
| 476 |
+
|
| 477 |
+
Returns:
|
| 478 |
+
The rotation as a quaternion tensor.
|
| 479 |
+
"""
|
| 480 |
+
if self._rot_mats is not None:
|
| 481 |
+
return rot_to_quat(self._rot_mats)
|
| 482 |
+
elif self._quats is not None:
|
| 483 |
+
return self._quats
|
| 484 |
+
else:
|
| 485 |
+
raise ValueError("Both rotations are None")
|
| 486 |
+
|
| 487 |
+
def get_cur_rot(self) -> torch.Tensor:
|
| 488 |
+
"""
|
| 489 |
+
Return the underlying rotation in its current form
|
| 490 |
+
|
| 491 |
+
Returns:
|
| 492 |
+
The stored rotation
|
| 493 |
+
"""
|
| 494 |
+
if self._rot_mats is not None:
|
| 495 |
+
return self._rot_mats
|
| 496 |
+
elif self._quats is not None:
|
| 497 |
+
return self._quats
|
| 498 |
+
else:
|
| 499 |
+
raise ValueError("Both rotations are None")
|
| 500 |
+
|
| 501 |
+
# Rotation functions
|
| 502 |
+
|
| 503 |
+
def compose_q_update_vec(self, q_update_vec: torch.Tensor, normalize_quats: bool = True) -> Rotation:
|
| 504 |
+
"""
|
| 505 |
+
Returns a new quaternion Rotation after updating the current object's underlying rotation with a quaternion
|
| 506 |
+
update, formatted as a [*, 3] tensor whose final three columns represent x, y, z such that (1, x, y, z) is the
|
| 507 |
+
desired (not necessarily unit) quaternion update.
|
| 508 |
+
|
| 509 |
+
Args:
|
| 510 |
+
q_update_vec:
|
| 511 |
+
A [*, 3] quaternion update tensor
|
| 512 |
+
normalize_quats:
|
| 513 |
+
Whether to normalize the output quaternion
|
| 514 |
+
Returns:
|
| 515 |
+
An updated Rotation
|
| 516 |
+
"""
|
| 517 |
+
quats = self.get_quats()
|
| 518 |
+
new_quats = quats + quat_multiply_by_vec(quats, q_update_vec)
|
| 519 |
+
return Rotation(
|
| 520 |
+
rot_mats=None,
|
| 521 |
+
quats=new_quats,
|
| 522 |
+
normalize_quats=normalize_quats,
|
| 523 |
+
)
|
| 524 |
+
|
| 525 |
+
def compose_r(self, r: Rotation) -> Rotation:
|
| 526 |
+
"""
|
| 527 |
+
Compose the rotation matrices of the current Rotation object with those of another.
|
| 528 |
+
|
| 529 |
+
Args:
|
| 530 |
+
r:
|
| 531 |
+
An update rotation object
|
| 532 |
+
Returns:
|
| 533 |
+
An updated rotation object
|
| 534 |
+
"""
|
| 535 |
+
r1 = self.get_rot_mats()
|
| 536 |
+
r2 = r.get_rot_mats()
|
| 537 |
+
new_rot_mats = rot_matmul(r1, r2)
|
| 538 |
+
return Rotation(rot_mats=new_rot_mats, quats=None)
|
| 539 |
+
|
| 540 |
+
def compose_q(self, r: Rotation, normalize_quats: bool = True) -> Rotation:
|
| 541 |
+
"""
|
| 542 |
+
Compose the quaternions of the current Rotation object with those of another.
|
| 543 |
+
|
| 544 |
+
Depending on whether either Rotation was initialized with quaternions, this function may call
|
| 545 |
+
torch.linalg.eigh.
|
| 546 |
+
|
| 547 |
+
Args:
|
| 548 |
+
r:
|
| 549 |
+
An update rotation object
|
| 550 |
+
Returns:
|
| 551 |
+
An updated rotation object
|
| 552 |
+
"""
|
| 553 |
+
q1 = self.get_quats()
|
| 554 |
+
q2 = r.get_quats()
|
| 555 |
+
new_quats = quat_multiply(q1, q2)
|
| 556 |
+
return Rotation(rot_mats=None, quats=new_quats, normalize_quats=normalize_quats)
|
| 557 |
+
|
| 558 |
+
def apply(self, pts: torch.Tensor) -> torch.Tensor:
|
| 559 |
+
"""
|
| 560 |
+
Apply the current Rotation as a rotation matrix to a set of 3D coordinates.
|
| 561 |
+
|
| 562 |
+
Args:
|
| 563 |
+
pts:
|
| 564 |
+
A [*, 3] set of points
|
| 565 |
+
Returns:
|
| 566 |
+
[*, 3] rotated points
|
| 567 |
+
"""
|
| 568 |
+
rot_mats = self.get_rot_mats()
|
| 569 |
+
return rot_vec_mul(rot_mats, pts)
|
| 570 |
+
|
| 571 |
+
def invert_apply(self, pts: torch.Tensor) -> torch.Tensor:
|
| 572 |
+
"""
|
| 573 |
+
The inverse of the apply() method.
|
| 574 |
+
|
| 575 |
+
Args:
|
| 576 |
+
pts:
|
| 577 |
+
A [*, 3] set of points
|
| 578 |
+
Returns:
|
| 579 |
+
[*, 3] inverse-rotated points
|
| 580 |
+
"""
|
| 581 |
+
rot_mats = self.get_rot_mats()
|
| 582 |
+
inv_rot_mats = invert_rot_mat(rot_mats)
|
| 583 |
+
return rot_vec_mul(inv_rot_mats, pts)
|
| 584 |
+
|
| 585 |
+
def invert(self) -> Rotation:
|
| 586 |
+
"""
|
| 587 |
+
Returns the inverse of the current Rotation.
|
| 588 |
+
|
| 589 |
+
Returns:
|
| 590 |
+
The inverse of the current Rotation
|
| 591 |
+
"""
|
| 592 |
+
if self._rot_mats is not None:
|
| 593 |
+
return Rotation(rot_mats=invert_rot_mat(self._rot_mats), quats=None)
|
| 594 |
+
elif self._quats is not None:
|
| 595 |
+
return Rotation(
|
| 596 |
+
rot_mats=None,
|
| 597 |
+
quats=invert_quat(self._quats),
|
| 598 |
+
normalize_quats=False,
|
| 599 |
+
)
|
| 600 |
+
else:
|
| 601 |
+
raise ValueError("Both rotations are None")
|
| 602 |
+
|
| 603 |
+
# "Tensor" stuff
|
| 604 |
+
|
| 605 |
+
def unsqueeze(self, dim: int) -> Rotation:
|
| 606 |
+
"""
|
| 607 |
+
Analogous to torch.unsqueeze. The dimension is relative to the shape of the Rotation object.
|
| 608 |
+
|
| 609 |
+
Args:
|
| 610 |
+
dim: A positive or negative dimension index.
|
| 611 |
+
Returns:
|
| 612 |
+
The unsqueezed Rotation.
|
| 613 |
+
"""
|
| 614 |
+
if dim >= len(self.shape):
|
| 615 |
+
raise ValueError("Invalid dimension")
|
| 616 |
+
|
| 617 |
+
if self._rot_mats is not None:
|
| 618 |
+
rot_mats = self._rot_mats.unsqueeze(dim if dim >= 0 else dim - 2)
|
| 619 |
+
return Rotation(rot_mats=rot_mats, quats=None)
|
| 620 |
+
elif self._quats is not None:
|
| 621 |
+
quats = self._quats.unsqueeze(dim if dim >= 0 else dim - 1)
|
| 622 |
+
return Rotation(rot_mats=None, quats=quats, normalize_quats=False)
|
| 623 |
+
else:
|
| 624 |
+
raise ValueError("Both rotations are None")
|
| 625 |
+
|
| 626 |
+
@staticmethod
|
| 627 |
+
def cat(rs: Sequence[Rotation], dim: int) -> Rotation:
|
| 628 |
+
"""
|
| 629 |
+
Concatenates rotations along one of the batch dimensions. Analogous to torch.cat().
|
| 630 |
+
|
| 631 |
+
Note that the output of this operation is always a rotation matrix, regardless of the format of input
|
| 632 |
+
rotations.
|
| 633 |
+
|
| 634 |
+
Args:
|
| 635 |
+
rs:
|
| 636 |
+
A list of rotation objects
|
| 637 |
+
dim:
|
| 638 |
+
The dimension along which the rotations should be concatenated
|
| 639 |
+
Returns:
|
| 640 |
+
A concatenated Rotation object in rotation matrix format
|
| 641 |
+
"""
|
| 642 |
+
rot_mats = torch.cat(
|
| 643 |
+
[r.get_rot_mats() for r in rs],
|
| 644 |
+
dim=dim if dim >= 0 else dim - 2,
|
| 645 |
+
)
|
| 646 |
+
|
| 647 |
+
return Rotation(rot_mats=rot_mats, quats=None)
|
| 648 |
+
|
| 649 |
+
def map_tensor_fn(self, fn: Callable[[torch.Tensor], torch.Tensor]) -> Rotation:
|
| 650 |
+
"""
|
| 651 |
+
Apply a Tensor -> Tensor function to underlying rotation tensors, mapping over the rotation dimension(s). Can
|
| 652 |
+
be used e.g. to sum out a one-hot batch dimension.
|
| 653 |
+
|
| 654 |
+
Args:
|
| 655 |
+
fn:
|
| 656 |
+
A Tensor -> Tensor function to be mapped over the Rotation
|
| 657 |
+
Returns:
|
| 658 |
+
The transformed Rotation object
|
| 659 |
+
"""
|
| 660 |
+
if self._rot_mats is not None:
|
| 661 |
+
rot_mats = self._rot_mats.view(self._rot_mats.shape[:-2] + (9,))
|
| 662 |
+
rot_mats = torch.stack(list(map(fn, torch.unbind(rot_mats, dim=-1))), dim=-1)
|
| 663 |
+
rot_mats = rot_mats.view(rot_mats.shape[:-1] + (3, 3))
|
| 664 |
+
return Rotation(rot_mats=rot_mats, quats=None)
|
| 665 |
+
elif self._quats is not None:
|
| 666 |
+
quats = torch.stack(list(map(fn, torch.unbind(self._quats, dim=-1))), dim=-1)
|
| 667 |
+
return Rotation(rot_mats=None, quats=quats, normalize_quats=False)
|
| 668 |
+
else:
|
| 669 |
+
raise ValueError("Both rotations are None")
|
| 670 |
+
|
| 671 |
+
def cuda(self) -> Rotation:
|
| 672 |
+
"""
|
| 673 |
+
Analogous to the cuda() method of torch Tensors
|
| 674 |
+
|
| 675 |
+
Returns:
|
| 676 |
+
A copy of the Rotation in CUDA memory
|
| 677 |
+
"""
|
| 678 |
+
if self._rot_mats is not None:
|
| 679 |
+
return Rotation(rot_mats=self._rot_mats.cuda(), quats=None)
|
| 680 |
+
elif self._quats is not None:
|
| 681 |
+
return Rotation(rot_mats=None, quats=self._quats.cuda(), normalize_quats=False)
|
| 682 |
+
else:
|
| 683 |
+
raise ValueError("Both rotations are None")
|
| 684 |
+
|
| 685 |
+
def to(self, device: torch.device | None, dtype: torch.dtype | None) -> Rotation:
|
| 686 |
+
"""
|
| 687 |
+
Analogous to the to() method of torch Tensors
|
| 688 |
+
|
| 689 |
+
Args:
|
| 690 |
+
device:
|
| 691 |
+
A torch device
|
| 692 |
+
dtype:
|
| 693 |
+
A torch dtype
|
| 694 |
+
Returns:
|
| 695 |
+
A copy of the Rotation using the new device and dtype
|
| 696 |
+
"""
|
| 697 |
+
if self._rot_mats is not None:
|
| 698 |
+
return Rotation(
|
| 699 |
+
rot_mats=self._rot_mats.to(device=device, dtype=dtype),
|
| 700 |
+
quats=None,
|
| 701 |
+
)
|
| 702 |
+
elif self._quats is not None:
|
| 703 |
+
return Rotation(
|
| 704 |
+
rot_mats=None,
|
| 705 |
+
quats=self._quats.to(device=device, dtype=dtype),
|
| 706 |
+
normalize_quats=False,
|
| 707 |
+
)
|
| 708 |
+
else:
|
| 709 |
+
raise ValueError("Both rotations are None")
|
| 710 |
+
|
| 711 |
+
def detach(self) -> Rotation:
|
| 712 |
+
"""
|
| 713 |
+
Returns a copy of the Rotation whose underlying Tensor has been detached from its torch graph.
|
| 714 |
+
|
| 715 |
+
Returns:
|
| 716 |
+
A copy of the Rotation whose underlying Tensor has been detached from its torch graph
|
| 717 |
+
"""
|
| 718 |
+
if self._rot_mats is not None:
|
| 719 |
+
return Rotation(rot_mats=self._rot_mats.detach(), quats=None)
|
| 720 |
+
elif self._quats is not None:
|
| 721 |
+
return Rotation(
|
| 722 |
+
rot_mats=None,
|
| 723 |
+
quats=self._quats.detach(),
|
| 724 |
+
normalize_quats=False,
|
| 725 |
+
)
|
| 726 |
+
else:
|
| 727 |
+
raise ValueError("Both rotations are None")
|
| 728 |
+
|
| 729 |
+
|
| 730 |
+
class Rigid:
|
| 731 |
+
"""
|
| 732 |
+
A class representing a rigid transformation. Little more than a wrapper around two objects: a Rotation object and a
|
| 733 |
+
[*, 3] translation Designed to behave approximately like a single torch tensor with the shape of the shared batch
|
| 734 |
+
dimensions of its component parts.
|
| 735 |
+
"""
|
| 736 |
+
|
| 737 |
+
def __init__(self, rots: Rotation | None, trans: torch.Tensor | None):
|
| 738 |
+
"""
|
| 739 |
+
Args:
|
| 740 |
+
rots: A [*, 3, 3] rotation tensor
|
| 741 |
+
trans: A corresponding [*, 3] translation tensor
|
| 742 |
+
"""
|
| 743 |
+
# (we need device, dtype, etc. from at least one input)
|
| 744 |
+
|
| 745 |
+
batch_dims, dtype, device, requires_grad = None, None, None, None
|
| 746 |
+
if trans is not None:
|
| 747 |
+
batch_dims = trans.shape[:-1]
|
| 748 |
+
dtype = trans.dtype
|
| 749 |
+
device = trans.device
|
| 750 |
+
requires_grad = trans.requires_grad
|
| 751 |
+
elif rots is not None:
|
| 752 |
+
batch_dims = rots.shape
|
| 753 |
+
dtype = rots.dtype
|
| 754 |
+
device = rots.device
|
| 755 |
+
requires_grad = rots.requires_grad
|
| 756 |
+
else:
|
| 757 |
+
raise ValueError("At least one input argument must be specified")
|
| 758 |
+
|
| 759 |
+
if rots is None:
|
| 760 |
+
rots = Rotation.identity(
|
| 761 |
+
batch_dims,
|
| 762 |
+
dtype,
|
| 763 |
+
device,
|
| 764 |
+
requires_grad,
|
| 765 |
+
)
|
| 766 |
+
elif trans is None:
|
| 767 |
+
trans = identity_trans(
|
| 768 |
+
batch_dims,
|
| 769 |
+
dtype,
|
| 770 |
+
device,
|
| 771 |
+
requires_grad,
|
| 772 |
+
)
|
| 773 |
+
|
| 774 |
+
assert rots is not None
|
| 775 |
+
assert trans is not None
|
| 776 |
+
|
| 777 |
+
if (rots.shape != trans.shape[:-1]) or (rots.device != trans.device):
|
| 778 |
+
raise ValueError("Rots and trans incompatible")
|
| 779 |
+
|
| 780 |
+
# Force full precision. Happens to the rotations automatically.
|
| 781 |
+
trans = trans.to(dtype=torch.float32)
|
| 782 |
+
|
| 783 |
+
self._rots = rots
|
| 784 |
+
self._trans = trans
|
| 785 |
+
|
| 786 |
+
@staticmethod
|
| 787 |
+
def identity(
|
| 788 |
+
shape: tuple[int, ...],
|
| 789 |
+
dtype: torch.dtype | None = None,
|
| 790 |
+
device: torch.device | None = None,
|
| 791 |
+
requires_grad: bool = True,
|
| 792 |
+
fmt: str = "quat",
|
| 793 |
+
) -> Rigid:
|
| 794 |
+
"""
|
| 795 |
+
Constructs an identity transformation.
|
| 796 |
+
|
| 797 |
+
Args:
|
| 798 |
+
shape:
|
| 799 |
+
The desired shape
|
| 800 |
+
dtype:
|
| 801 |
+
The dtype of both internal tensors
|
| 802 |
+
device:
|
| 803 |
+
The device of both internal tensors
|
| 804 |
+
requires_grad:
|
| 805 |
+
Whether grad should be enabled for the internal tensors
|
| 806 |
+
Returns:
|
| 807 |
+
The identity transformation
|
| 808 |
+
"""
|
| 809 |
+
return Rigid(
|
| 810 |
+
Rotation.identity(shape, dtype, device, requires_grad, fmt=fmt),
|
| 811 |
+
identity_trans(shape, dtype, device, requires_grad),
|
| 812 |
+
)
|
| 813 |
+
|
| 814 |
+
def __getitem__(self, index: Any) -> Rigid:
|
| 815 |
+
"""
|
| 816 |
+
Indexes the affine transformation with PyTorch-style indices. The index is applied to the shared dimensions of
|
| 817 |
+
both the rotation and the translation.
|
| 818 |
+
|
| 819 |
+
E.g.::
|
| 820 |
+
|
| 821 |
+
r = Rotation(rot_mats=torch.rand(10, 10, 3, 3), quats=None) t = Rigid(r, torch.rand(10, 10, 3)) indexed =
|
| 822 |
+
t[3, 4:6] assert(indexed.shape == (2,)) assert(indexed.get_rots().shape == (2,))
|
| 823 |
+
assert(indexed.get_trans().shape == (2, 3))
|
| 824 |
+
|
| 825 |
+
Args:
|
| 826 |
+
index: A standard torch tensor index. E.g. 8, (10, None, 3),
|
| 827 |
+
or (3, slice(0, 1, None))
|
| 828 |
+
Returns:
|
| 829 |
+
The indexed tensor
|
| 830 |
+
"""
|
| 831 |
+
if type(index) is not tuple:
|
| 832 |
+
index = (index,)
|
| 833 |
+
|
| 834 |
+
return Rigid(
|
| 835 |
+
self._rots[index],
|
| 836 |
+
self._trans[index + (slice(None),)],
|
| 837 |
+
)
|
| 838 |
+
|
| 839 |
+
def __mul__(self, right: torch.Tensor) -> Rigid:
|
| 840 |
+
"""
|
| 841 |
+
Pointwise left multiplication of the transformation with a tensor. Can be used to e.g. mask the Rigid.
|
| 842 |
+
|
| 843 |
+
Args:
|
| 844 |
+
right:
|
| 845 |
+
The tensor multiplicand
|
| 846 |
+
Returns:
|
| 847 |
+
The product
|
| 848 |
+
"""
|
| 849 |
+
if not (isinstance(right, torch.Tensor)):
|
| 850 |
+
raise TypeError("The other multiplicand must be a Tensor")
|
| 851 |
+
|
| 852 |
+
new_rots = self._rots * right
|
| 853 |
+
new_trans = self._trans * right[..., None]
|
| 854 |
+
|
| 855 |
+
return Rigid(new_rots, new_trans)
|
| 856 |
+
|
| 857 |
+
def __rmul__(self, left: torch.Tensor) -> Rigid:
|
| 858 |
+
"""
|
| 859 |
+
Reverse pointwise multiplication of the transformation with a tensor.
|
| 860 |
+
|
| 861 |
+
Args:
|
| 862 |
+
left:
|
| 863 |
+
The left multiplicand
|
| 864 |
+
Returns:
|
| 865 |
+
The product
|
| 866 |
+
"""
|
| 867 |
+
return self.__mul__(left)
|
| 868 |
+
|
| 869 |
+
@property
|
| 870 |
+
def shape(self) -> torch.Size:
|
| 871 |
+
"""
|
| 872 |
+
Returns the shape of the shared dimensions of the rotation and the translation.
|
| 873 |
+
|
| 874 |
+
Returns:
|
| 875 |
+
The shape of the transformation
|
| 876 |
+
"""
|
| 877 |
+
return self._trans.shape[:-1]
|
| 878 |
+
|
| 879 |
+
@property
|
| 880 |
+
def device(self) -> torch.device:
|
| 881 |
+
"""
|
| 882 |
+
Returns the device on which the Rigid's tensors are located.
|
| 883 |
+
|
| 884 |
+
Returns:
|
| 885 |
+
The device on which the Rigid's tensors are located
|
| 886 |
+
"""
|
| 887 |
+
return self._trans.device
|
| 888 |
+
|
| 889 |
+
def get_rots(self) -> Rotation:
|
| 890 |
+
"""
|
| 891 |
+
Getter for the rotation.
|
| 892 |
+
|
| 893 |
+
Returns:
|
| 894 |
+
The rotation object
|
| 895 |
+
"""
|
| 896 |
+
return self._rots
|
| 897 |
+
|
| 898 |
+
def get_trans(self) -> torch.Tensor:
|
| 899 |
+
"""
|
| 900 |
+
Getter for the translation.
|
| 901 |
+
|
| 902 |
+
Returns:
|
| 903 |
+
The stored translation
|
| 904 |
+
"""
|
| 905 |
+
return self._trans
|
| 906 |
+
|
| 907 |
+
def compose_q_update_vec(self, q_update_vec: torch.Tensor) -> Rigid:
|
| 908 |
+
"""
|
| 909 |
+
Composes the transformation with a quaternion update vector of shape [*, 6], where the final 6 columns
|
| 910 |
+
represent the x, y, and z values of a quaternion of form (1, x, y, z) followed by a 3D translation.
|
| 911 |
+
|
| 912 |
+
Args:
|
| 913 |
+
q_vec: The quaternion update vector.
|
| 914 |
+
Returns:
|
| 915 |
+
The composed transformation.
|
| 916 |
+
"""
|
| 917 |
+
q_vec, t_vec = q_update_vec[..., :3], q_update_vec[..., 3:]
|
| 918 |
+
new_rots = self._rots.compose_q_update_vec(q_vec)
|
| 919 |
+
|
| 920 |
+
trans_update = self._rots.apply(t_vec)
|
| 921 |
+
new_translation = self._trans + trans_update
|
| 922 |
+
|
| 923 |
+
return Rigid(new_rots, new_translation)
|
| 924 |
+
|
| 925 |
+
def compose(self, r: Rigid) -> Rigid:
|
| 926 |
+
"""
|
| 927 |
+
Composes the current rigid object with another.
|
| 928 |
+
|
| 929 |
+
Args:
|
| 930 |
+
r:
|
| 931 |
+
Another Rigid object
|
| 932 |
+
Returns:
|
| 933 |
+
The composition of the two transformations
|
| 934 |
+
"""
|
| 935 |
+
new_rot = self._rots.compose_r(r._rots)
|
| 936 |
+
new_trans = self._rots.apply(r._trans) + self._trans
|
| 937 |
+
return Rigid(new_rot, new_trans)
|
| 938 |
+
|
| 939 |
+
def apply(self, pts: torch.Tensor) -> torch.Tensor:
|
| 940 |
+
"""
|
| 941 |
+
Applies the transformation to a coordinate tensor.
|
| 942 |
+
|
| 943 |
+
Args:
|
| 944 |
+
pts: A [*, 3] coordinate tensor.
|
| 945 |
+
Returns:
|
| 946 |
+
The transformed points.
|
| 947 |
+
"""
|
| 948 |
+
rotated = self._rots.apply(pts)
|
| 949 |
+
return rotated + self._trans
|
| 950 |
+
|
| 951 |
+
def invert_apply(self, pts: torch.Tensor) -> torch.Tensor:
|
| 952 |
+
"""
|
| 953 |
+
Applies the inverse of the transformation to a coordinate tensor.
|
| 954 |
+
|
| 955 |
+
Args:
|
| 956 |
+
pts: A [*, 3] coordinate tensor
|
| 957 |
+
Returns:
|
| 958 |
+
The transformed points.
|
| 959 |
+
"""
|
| 960 |
+
pts = pts - self._trans
|
| 961 |
+
return self._rots.invert_apply(pts)
|
| 962 |
+
|
| 963 |
+
def invert(self) -> Rigid:
|
| 964 |
+
"""
|
| 965 |
+
Inverts the transformation.
|
| 966 |
+
|
| 967 |
+
Returns:
|
| 968 |
+
The inverse transformation.
|
| 969 |
+
"""
|
| 970 |
+
rot_inv = self._rots.invert()
|
| 971 |
+
trn_inv = rot_inv.apply(self._trans)
|
| 972 |
+
|
| 973 |
+
return Rigid(rot_inv, -1 * trn_inv)
|
| 974 |
+
|
| 975 |
+
def map_tensor_fn(self, fn: Callable[[torch.Tensor], torch.Tensor]) -> Rigid:
|
| 976 |
+
"""
|
| 977 |
+
Apply a Tensor -> Tensor function to underlying translation and rotation tensors, mapping over the
|
| 978 |
+
translation/rotation dimensions respectively.
|
| 979 |
+
|
| 980 |
+
Args:
|
| 981 |
+
fn:
|
| 982 |
+
A Tensor -> Tensor function to be mapped over the Rigid
|
| 983 |
+
Returns:
|
| 984 |
+
The transformed Rigid object
|
| 985 |
+
"""
|
| 986 |
+
new_rots = self._rots.map_tensor_fn(fn)
|
| 987 |
+
new_trans = torch.stack(list(map(fn, torch.unbind(self._trans, dim=-1))), dim=-1)
|
| 988 |
+
|
| 989 |
+
return Rigid(new_rots, new_trans)
|
| 990 |
+
|
| 991 |
+
def to_tensor_4x4(self) -> torch.Tensor:
|
| 992 |
+
"""
|
| 993 |
+
Converts a transformation to a homogeneous transformation tensor.
|
| 994 |
+
|
| 995 |
+
Returns:
|
| 996 |
+
A [*, 4, 4] homogeneous transformation tensor
|
| 997 |
+
"""
|
| 998 |
+
tensor = self._trans.new_zeros((*self.shape, 4, 4))
|
| 999 |
+
tensor[..., :3, :3] = self._rots.get_rot_mats()
|
| 1000 |
+
tensor[..., :3, 3] = self._trans
|
| 1001 |
+
tensor[..., 3, 3] = 1
|
| 1002 |
+
return tensor
|
| 1003 |
+
|
| 1004 |
+
@staticmethod
|
| 1005 |
+
def from_tensor_4x4(t: torch.Tensor) -> Rigid:
|
| 1006 |
+
"""
|
| 1007 |
+
Constructs a transformation from a homogeneous transformation tensor.
|
| 1008 |
+
|
| 1009 |
+
Args:
|
| 1010 |
+
t: [*, 4, 4] homogeneous transformation tensor
|
| 1011 |
+
Returns:
|
| 1012 |
+
T object with shape [*]
|
| 1013 |
+
"""
|
| 1014 |
+
if t.shape[-2:] != (4, 4):
|
| 1015 |
+
raise ValueError("Incorrectly shaped input tensor")
|
| 1016 |
+
|
| 1017 |
+
rots = Rotation(rot_mats=t[..., :3, :3], quats=None)
|
| 1018 |
+
trans = t[..., :3, 3]
|
| 1019 |
+
|
| 1020 |
+
return Rigid(rots, trans)
|
| 1021 |
+
|
| 1022 |
+
def to_tensor_7(self) -> torch.Tensor:
|
| 1023 |
+
"""
|
| 1024 |
+
Converts a transformation to a tensor with 7 final columns, four for the quaternion followed by three for the
|
| 1025 |
+
translation.
|
| 1026 |
+
|
| 1027 |
+
Returns:
|
| 1028 |
+
A [*, 7] tensor representation of the transformation
|
| 1029 |
+
"""
|
| 1030 |
+
tensor = self._trans.new_zeros((*self.shape, 7))
|
| 1031 |
+
tensor[..., :4] = self._rots.get_quats()
|
| 1032 |
+
tensor[..., 4:] = self._trans
|
| 1033 |
+
|
| 1034 |
+
return tensor
|
| 1035 |
+
|
| 1036 |
+
@staticmethod
|
| 1037 |
+
def from_tensor_7(t: torch.Tensor, normalize_quats: bool = False) -> Rigid:
|
| 1038 |
+
if t.shape[-1] != 7:
|
| 1039 |
+
raise ValueError("Incorrectly shaped input tensor")
|
| 1040 |
+
|
| 1041 |
+
quats, trans = t[..., :4], t[..., 4:]
|
| 1042 |
+
|
| 1043 |
+
rots = Rotation(rot_mats=None, quats=quats, normalize_quats=normalize_quats)
|
| 1044 |
+
|
| 1045 |
+
return Rigid(rots, trans)
|
| 1046 |
+
|
| 1047 |
+
@staticmethod
|
| 1048 |
+
def from_3_points(
|
| 1049 |
+
p_neg_x_axis: torch.Tensor, origin: torch.Tensor, p_xy_plane: torch.Tensor, eps: float = 1e-8
|
| 1050 |
+
) -> Rigid:
|
| 1051 |
+
"""
|
| 1052 |
+
Implements algorithm 21. Constructs transformations from sets of 3 points using the Gram-Schmidt algorithm.
|
| 1053 |
+
|
| 1054 |
+
Args:
|
| 1055 |
+
p_neg_x_axis: [*, 3] coordinates
|
| 1056 |
+
origin: [*, 3] coordinates used as frame origins
|
| 1057 |
+
p_xy_plane: [*, 3] coordinates
|
| 1058 |
+
eps: Small epsilon value
|
| 1059 |
+
Returns:
|
| 1060 |
+
A transformation object of shape [*]
|
| 1061 |
+
"""
|
| 1062 |
+
p_neg_x_axis_unbound = torch.unbind(p_neg_x_axis, dim=-1)
|
| 1063 |
+
origin_unbound = torch.unbind(origin, dim=-1)
|
| 1064 |
+
p_xy_plane_unbound = torch.unbind(p_xy_plane, dim=-1)
|
| 1065 |
+
|
| 1066 |
+
e0 = [c1 - c2 for c1, c2 in zip(origin_unbound, p_neg_x_axis_unbound)]
|
| 1067 |
+
e1 = [c1 - c2 for c1, c2 in zip(p_xy_plane_unbound, origin_unbound)]
|
| 1068 |
+
|
| 1069 |
+
denom = torch.sqrt(sum(c * c for c in e0) + eps * torch.ones_like(e0[0]))
|
| 1070 |
+
e0 = [c / denom for c in e0]
|
| 1071 |
+
dot = sum((c1 * c2 for c1, c2 in zip(e0, e1)))
|
| 1072 |
+
e1 = [c2 - c1 * dot for c1, c2 in zip(e0, e1)]
|
| 1073 |
+
denom = torch.sqrt(sum(c * c for c in e1) + eps * torch.ones_like(e1[0]))
|
| 1074 |
+
e1 = [c / denom for c in e1]
|
| 1075 |
+
e2 = [
|
| 1076 |
+
e0[1] * e1[2] - e0[2] * e1[1],
|
| 1077 |
+
e0[2] * e1[0] - e0[0] * e1[2],
|
| 1078 |
+
e0[0] * e1[1] - e0[1] * e1[0],
|
| 1079 |
+
]
|
| 1080 |
+
|
| 1081 |
+
rots = torch.stack([c for tup in zip(e0, e1, e2) for c in tup], dim=-1)
|
| 1082 |
+
rots = rots.reshape(rots.shape[:-1] + (3, 3))
|
| 1083 |
+
|
| 1084 |
+
rot_obj = Rotation(rot_mats=rots, quats=None)
|
| 1085 |
+
|
| 1086 |
+
return Rigid(rot_obj, torch.stack(origin_unbound, dim=-1))
|
| 1087 |
+
|
| 1088 |
+
def unsqueeze(self, dim: int) -> Rigid:
|
| 1089 |
+
"""
|
| 1090 |
+
Analogous to torch.unsqueeze. The dimension is relative to the shared dimensions of the rotation/translation.
|
| 1091 |
+
|
| 1092 |
+
Args:
|
| 1093 |
+
dim: A positive or negative dimension index.
|
| 1094 |
+
Returns:
|
| 1095 |
+
The unsqueezed transformation.
|
| 1096 |
+
"""
|
| 1097 |
+
if dim >= len(self.shape):
|
| 1098 |
+
raise ValueError("Invalid dimension")
|
| 1099 |
+
rots = self._rots.unsqueeze(dim)
|
| 1100 |
+
trans = self._trans.unsqueeze(dim if dim >= 0 else dim - 1)
|
| 1101 |
+
|
| 1102 |
+
return Rigid(rots, trans)
|
| 1103 |
+
|
| 1104 |
+
@staticmethod
|
| 1105 |
+
def cat(ts: Sequence[Rigid], dim: int) -> Rigid:
|
| 1106 |
+
"""
|
| 1107 |
+
Concatenates transformations along a new dimension.
|
| 1108 |
+
|
| 1109 |
+
Args:
|
| 1110 |
+
ts:
|
| 1111 |
+
A list of T objects
|
| 1112 |
+
dim:
|
| 1113 |
+
The dimension along which the transformations should be concatenated
|
| 1114 |
+
Returns:
|
| 1115 |
+
A concatenated transformation object
|
| 1116 |
+
"""
|
| 1117 |
+
rots = Rotation.cat([t._rots for t in ts], dim)
|
| 1118 |
+
trans = torch.cat([t._trans for t in ts], dim=dim if dim >= 0 else dim - 1)
|
| 1119 |
+
|
| 1120 |
+
return Rigid(rots, trans)
|
| 1121 |
+
|
| 1122 |
+
def apply_rot_fn(self, fn: Callable[[Rotation], Rotation]) -> Rigid:
|
| 1123 |
+
"""
|
| 1124 |
+
Applies a Rotation -> Rotation function to the stored rotation object.
|
| 1125 |
+
|
| 1126 |
+
Args:
|
| 1127 |
+
fn: A function of type Rotation -> Rotation
|
| 1128 |
+
Returns:
|
| 1129 |
+
A transformation object with a transformed rotation.
|
| 1130 |
+
"""
|
| 1131 |
+
return Rigid(fn(self._rots), self._trans)
|
| 1132 |
+
|
| 1133 |
+
def apply_trans_fn(self, fn: Callable[[torch.Tensor], torch.Tensor]) -> Rigid:
|
| 1134 |
+
"""
|
| 1135 |
+
Applies a Tensor -> Tensor function to the stored translation.
|
| 1136 |
+
|
| 1137 |
+
Args:
|
| 1138 |
+
fn:
|
| 1139 |
+
A function of type Tensor -> Tensor to be applied to the translation
|
| 1140 |
+
Returns:
|
| 1141 |
+
A transformation object with a transformed translation.
|
| 1142 |
+
"""
|
| 1143 |
+
return Rigid(self._rots, fn(self._trans))
|
| 1144 |
+
|
| 1145 |
+
def scale_translation(self, trans_scale_factor: float) -> Rigid:
|
| 1146 |
+
"""
|
| 1147 |
+
Scales the translation by a constant factor.
|
| 1148 |
+
|
| 1149 |
+
Args:
|
| 1150 |
+
trans_scale_factor:
|
| 1151 |
+
The constant factor
|
| 1152 |
+
Returns:
|
| 1153 |
+
A transformation object with a scaled translation.
|
| 1154 |
+
"""
|
| 1155 |
+
return self.apply_trans_fn(lambda t: t * trans_scale_factor)
|
| 1156 |
+
|
| 1157 |
+
def stop_rot_gradient(self) -> Rigid:
|
| 1158 |
+
"""
|
| 1159 |
+
Detaches the underlying rotation object
|
| 1160 |
+
|
| 1161 |
+
Returns:
|
| 1162 |
+
A transformation object with detached rotations
|
| 1163 |
+
"""
|
| 1164 |
+
return self.apply_rot_fn(lambda r: r.detach())
|
| 1165 |
+
|
| 1166 |
+
@staticmethod
|
| 1167 |
+
def make_transform_from_reference(
|
| 1168 |
+
n_xyz: torch.Tensor, ca_xyz: torch.Tensor, c_xyz: torch.Tensor, eps: float = 1e-20
|
| 1169 |
+
) -> Rigid:
|
| 1170 |
+
"""
|
| 1171 |
+
Returns a transformation object from reference coordinates.
|
| 1172 |
+
|
| 1173 |
+
Note that this method does not take care of symmetries. If you provide the atom positions in the non-standard
|
| 1174 |
+
way, the N atom will end up not at [-0.527250, 1.359329, 0.0] but instead at [-0.527250, -1.359329, 0.0]. You
|
| 1175 |
+
need to take care of such cases in your code.
|
| 1176 |
+
|
| 1177 |
+
Args:
|
| 1178 |
+
n_xyz: A [*, 3] tensor of nitrogen xyz coordinates.
|
| 1179 |
+
ca_xyz: A [*, 3] tensor of carbon alpha xyz coordinates.
|
| 1180 |
+
c_xyz: A [*, 3] tensor of carbon xyz coordinates.
|
| 1181 |
+
Returns:
|
| 1182 |
+
A transformation object. After applying the translation and rotation to the reference backbone, the
|
| 1183 |
+
coordinates will approximately equal to the input coordinates.
|
| 1184 |
+
"""
|
| 1185 |
+
translation = -1 * ca_xyz
|
| 1186 |
+
n_xyz = n_xyz + translation
|
| 1187 |
+
c_xyz = c_xyz + translation
|
| 1188 |
+
|
| 1189 |
+
c_x, c_y, c_z = [c_xyz[..., i] for i in range(3)]
|
| 1190 |
+
norm = torch.sqrt(eps + c_x**2 + c_y**2)
|
| 1191 |
+
sin_c1 = -c_y / norm
|
| 1192 |
+
cos_c1 = c_x / norm
|
| 1193 |
+
|
| 1194 |
+
c1_rots = sin_c1.new_zeros((*sin_c1.shape, 3, 3))
|
| 1195 |
+
c1_rots[..., 0, 0] = cos_c1
|
| 1196 |
+
c1_rots[..., 0, 1] = -1 * sin_c1
|
| 1197 |
+
c1_rots[..., 1, 0] = sin_c1
|
| 1198 |
+
c1_rots[..., 1, 1] = cos_c1
|
| 1199 |
+
c1_rots[..., 2, 2] = 1
|
| 1200 |
+
|
| 1201 |
+
norm = torch.sqrt(eps + c_x**2 + c_y**2 + c_z**2)
|
| 1202 |
+
sin_c2 = c_z / norm
|
| 1203 |
+
cos_c2 = torch.sqrt(c_x**2 + c_y**2) / norm
|
| 1204 |
+
|
| 1205 |
+
c2_rots = sin_c2.new_zeros((*sin_c2.shape, 3, 3))
|
| 1206 |
+
c2_rots[..., 0, 0] = cos_c2
|
| 1207 |
+
c2_rots[..., 0, 2] = sin_c2
|
| 1208 |
+
c2_rots[..., 1, 1] = 1
|
| 1209 |
+
c2_rots[..., 2, 0] = -1 * sin_c2
|
| 1210 |
+
c2_rots[..., 2, 2] = cos_c2
|
| 1211 |
+
|
| 1212 |
+
c_rots = rot_matmul(c2_rots, c1_rots)
|
| 1213 |
+
n_xyz = rot_vec_mul(c_rots, n_xyz)
|
| 1214 |
+
|
| 1215 |
+
_, n_y, n_z = [n_xyz[..., i] for i in range(3)]
|
| 1216 |
+
norm = torch.sqrt(eps + n_y**2 + n_z**2)
|
| 1217 |
+
sin_n = -n_z / norm
|
| 1218 |
+
cos_n = n_y / norm
|
| 1219 |
+
|
| 1220 |
+
n_rots = sin_c2.new_zeros((*sin_c2.shape, 3, 3))
|
| 1221 |
+
n_rots[..., 0, 0] = 1
|
| 1222 |
+
n_rots[..., 1, 1] = cos_n
|
| 1223 |
+
n_rots[..., 1, 2] = -1 * sin_n
|
| 1224 |
+
n_rots[..., 2, 1] = sin_n
|
| 1225 |
+
n_rots[..., 2, 2] = cos_n
|
| 1226 |
+
|
| 1227 |
+
rots = rot_matmul(n_rots, c_rots)
|
| 1228 |
+
|
| 1229 |
+
rots = rots.transpose(-1, -2)
|
| 1230 |
+
translation = -1 * translation
|
| 1231 |
+
|
| 1232 |
+
rot_obj = Rotation(rot_mats=rots, quats=None)
|
| 1233 |
+
|
| 1234 |
+
return Rigid(rot_obj, translation)
|
| 1235 |
+
|
| 1236 |
+
def cuda(self) -> Rigid:
|
| 1237 |
+
"""
|
| 1238 |
+
Moves the transformation object to GPU memory
|
| 1239 |
+
|
| 1240 |
+
Returns:
|
| 1241 |
+
A version of the transformation on GPU
|
| 1242 |
+
"""
|
| 1243 |
+
return Rigid(self._rots.cuda(), self._trans.cuda())
|
LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/ovis2/modeling_ovis2.py
ADDED
|
@@ -0,0 +1,730 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
|
| 2 |
+
# This file was automatically generated from src/transformers/models/ovis2/modular_ovis2.py.
|
| 3 |
+
# Do NOT edit this file manually as any edits will be overwritten by the generation of
|
| 4 |
+
# the file from the modular. If any change should be done, please apply the change to the
|
| 5 |
+
# modular_ovis2.py file directly. One of our CI enforces this.
|
| 6 |
+
# 🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
|
| 7 |
+
# Copyright 2025 The HuggingFace Inc. team. All rights reserved.
|
| 8 |
+
#
|
| 9 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 10 |
+
# you may not use this file except in compliance with the License.
|
| 11 |
+
# You may obtain a copy of the License at
|
| 12 |
+
#
|
| 13 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 14 |
+
#
|
| 15 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 16 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 17 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 18 |
+
# See the License for the specific language governing permissions and
|
| 19 |
+
# limitations under the License.
|
| 20 |
+
|
| 21 |
+
import math
|
| 22 |
+
from collections.abc import Callable
|
| 23 |
+
from dataclasses import dataclass
|
| 24 |
+
|
| 25 |
+
import torch
|
| 26 |
+
from torch import nn
|
| 27 |
+
|
| 28 |
+
from ... import initialization as init
|
| 29 |
+
from ...activations import ACT2FN
|
| 30 |
+
from ...cache_utils import Cache
|
| 31 |
+
from ...generation import GenerationMixin
|
| 32 |
+
from ...integrations import use_kernel_forward_from_hub
|
| 33 |
+
from ...modeling_layers import GradientCheckpointingLayer
|
| 34 |
+
from ...modeling_outputs import BaseModelOutput, BaseModelOutputWithPast, BaseModelOutputWithPooling
|
| 35 |
+
from ...modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel
|
| 36 |
+
from ...processing_utils import Unpack
|
| 37 |
+
from ...utils import ModelOutput, TransformersKwargs, auto_docstring, can_return_tuple, torch_compilable_check
|
| 38 |
+
from ...utils.generic import merge_with_config_defaults
|
| 39 |
+
from ...utils.output_capturing import capture_outputs
|
| 40 |
+
from ..auto import AutoModel
|
| 41 |
+
from .configuration_ovis2 import Ovis2Config, Ovis2VisionConfig
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
@auto_docstring
|
| 45 |
+
@dataclass
|
| 46 |
+
class BaseModelOutputWithVisualIndicatorFeatures(BaseModelOutputWithPooling):
|
| 47 |
+
r"""
|
| 48 |
+
visual_indicator_features (`torch.FloatTensor` of shape `(batch_size, visual_indicator_size)`):
|
| 49 |
+
Visual indicator features extracted from the model, which can be used for auxiliary tasks or further processing.
|
| 50 |
+
"""
|
| 51 |
+
|
| 52 |
+
visual_indicator_features: torch.FloatTensor | None = None
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
@auto_docstring(
|
| 56 |
+
custom_intro="""
|
| 57 |
+
Base class for Llava outputs, with hidden states and attentions.
|
| 58 |
+
"""
|
| 59 |
+
)
|
| 60 |
+
@dataclass
|
| 61 |
+
class Ovis2ModelOutputWithPast(BaseModelOutputWithPast):
|
| 62 |
+
r"""
|
| 63 |
+
past_key_values (`Cache`, *optional*, returned when `use_cache=True` is passed or when `config.use_cache=True`):
|
| 64 |
+
It is a [`~cache_utils.Cache`] instance. For more details, see our [kv cache guide](https://huggingface.co/docs/transformers/en/kv_cache).
|
| 65 |
+
|
| 66 |
+
Contains pre-computed hidden-states (key and values in the self-attention blocks) that can be used (see
|
| 67 |
+
`past_key_values` input) to speed up sequential decoding.
|
| 68 |
+
image_hidden_states (`torch.FloatTensor`, *optional*):
|
| 69 |
+
A `torch.FloatTensor` of size `(batch_size, num_images, sequence_length, hidden_size)`.
|
| 70 |
+
image_hidden_states of the model produced by the vision encoder and after projecting the last hidden state.
|
| 71 |
+
"""
|
| 72 |
+
|
| 73 |
+
image_hidden_states: torch.FloatTensor | None = None
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
@auto_docstring(
|
| 77 |
+
custom_intro="""
|
| 78 |
+
Base class for Ovis2 causal language model (or autoregressive) outputs.
|
| 79 |
+
"""
|
| 80 |
+
)
|
| 81 |
+
@dataclass
|
| 82 |
+
class Ovis2CausalLMOutputWithPast(ModelOutput):
|
| 83 |
+
r"""
|
| 84 |
+
loss (`torch.FloatTensor` of shape `(1,)`, *optional*, returned when `labels` is provided):
|
| 85 |
+
Language modeling loss (for next-token prediction).
|
| 86 |
+
logits (`torch.FloatTensor` of shape `(batch_size, sequence_length, config.vocab_size)`):
|
| 87 |
+
Prediction scores of the language modeling head (scores for each vocabulary token before SoftMax).
|
| 88 |
+
past_key_values (`Cache`, *optional*, returned when `use_cache=True` is passed or when `config.use_cache=True`):
|
| 89 |
+
It is a [`~cache_utils.Cache`] instance. For more details, see our [kv cache guide](https://huggingface.co/docs/transformers/en/kv_cache).
|
| 90 |
+
|
| 91 |
+
Contains pre-computed hidden-states (key and values in the self-attention blocks) that can be used (see
|
| 92 |
+
`past_key_values` input) to speed up sequential decoding.
|
| 93 |
+
image_hidden_states (`torch.FloatTensor`, *optional*):
|
| 94 |
+
A `torch.FloatTensor` of size (batch_size * num_patches, num_images, sequence_length, hidden_size)`.
|
| 95 |
+
image_hidden_states of the model produced by the vision encoder and after projecting the last hidden state.
|
| 96 |
+
"""
|
| 97 |
+
|
| 98 |
+
loss: torch.FloatTensor | None = None
|
| 99 |
+
logits: torch.FloatTensor | None = None
|
| 100 |
+
past_key_values: Cache | None = None
|
| 101 |
+
hidden_states: tuple[torch.FloatTensor] | None = None
|
| 102 |
+
attentions: tuple[torch.FloatTensor] | None = None
|
| 103 |
+
image_hidden_states: torch.FloatTensor | None = None
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
@use_kernel_forward_from_hub("RMSNorm")
|
| 107 |
+
class Ovis2RMSNorm(nn.Module):
|
| 108 |
+
def __init__(self, hidden_size, eps: float = 1e-6) -> None:
|
| 109 |
+
"""
|
| 110 |
+
Ovis2RMSNorm is equivalent to T5LayerNorm
|
| 111 |
+
"""
|
| 112 |
+
super().__init__()
|
| 113 |
+
self.weight = nn.Parameter(torch.ones(hidden_size))
|
| 114 |
+
self.variance_epsilon = eps
|
| 115 |
+
|
| 116 |
+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
| 117 |
+
input_dtype = hidden_states.dtype
|
| 118 |
+
hidden_states = hidden_states.to(torch.float32)
|
| 119 |
+
variance = hidden_states.pow(2).mean(-1, keepdim=True)
|
| 120 |
+
hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
|
| 121 |
+
return self.weight * hidden_states.to(input_dtype)
|
| 122 |
+
|
| 123 |
+
def extra_repr(self):
|
| 124 |
+
return f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}"
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
class Ovis2VisionMLP(nn.Module):
|
| 128 |
+
def __init__(self, config):
|
| 129 |
+
super().__init__()
|
| 130 |
+
self.config = config
|
| 131 |
+
self.hidden_size = config.hidden_size
|
| 132 |
+
self.intermediate_size = config.intermediate_size
|
| 133 |
+
self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=config.mlp_bias)
|
| 134 |
+
self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=config.mlp_bias)
|
| 135 |
+
self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=config.mlp_bias)
|
| 136 |
+
self.act_fn = ACT2FN[config.hidden_act]
|
| 137 |
+
|
| 138 |
+
def forward(self, x):
|
| 139 |
+
down_proj = self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))
|
| 140 |
+
return down_proj
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
class Ovis2VisionEmbeddings(nn.Module):
|
| 144 |
+
def __init__(self, config: Ovis2VisionConfig):
|
| 145 |
+
super().__init__()
|
| 146 |
+
self.config = config
|
| 147 |
+
self.embed_dim = config.hidden_size
|
| 148 |
+
self.image_size = config.image_size
|
| 149 |
+
self.patch_size = config.patch_size
|
| 150 |
+
|
| 151 |
+
self.patch_embedding = nn.Conv2d(
|
| 152 |
+
in_channels=config.num_channels,
|
| 153 |
+
out_channels=self.embed_dim,
|
| 154 |
+
kernel_size=self.patch_size,
|
| 155 |
+
stride=self.patch_size,
|
| 156 |
+
padding="valid",
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
self.num_patches = (self.image_size // self.patch_size) ** 2
|
| 160 |
+
self.num_positions = self.num_patches
|
| 161 |
+
self.position_embedding = nn.Embedding(self.num_positions, self.embed_dim)
|
| 162 |
+
self.register_buffer("position_ids", torch.arange(self.num_positions).expand((1, -1)), persistent=False)
|
| 163 |
+
self.rms_norm = Ovis2RMSNorm(config.hidden_size, config.rms_norm_eps)
|
| 164 |
+
|
| 165 |
+
def forward(self, pixel_values: torch.FloatTensor) -> torch.Tensor:
|
| 166 |
+
target_dtype = self.patch_embedding.weight.dtype
|
| 167 |
+
patch_embeds = self.patch_embedding(pixel_values.to(dtype=target_dtype))
|
| 168 |
+
embeddings = patch_embeds.flatten(2).transpose(1, 2)
|
| 169 |
+
embeddings = self.rms_norm(embeddings)
|
| 170 |
+
|
| 171 |
+
embeddings = embeddings + self.position_embedding(self.position_ids)
|
| 172 |
+
|
| 173 |
+
return embeddings
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def eager_attention_forward(
|
| 177 |
+
module: nn.Module,
|
| 178 |
+
query: torch.Tensor,
|
| 179 |
+
key: torch.Tensor,
|
| 180 |
+
value: torch.Tensor,
|
| 181 |
+
attention_mask: torch.Tensor | None,
|
| 182 |
+
scaling: float,
|
| 183 |
+
dropout: float = 0.0,
|
| 184 |
+
**kwargs,
|
| 185 |
+
):
|
| 186 |
+
attn_weights = torch.matmul(query, key.transpose(-1, -2)) * scaling
|
| 187 |
+
if attention_mask is not None:
|
| 188 |
+
attn_weights = attn_weights + attention_mask
|
| 189 |
+
|
| 190 |
+
attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query.dtype)
|
| 191 |
+
attn_weights = nn.functional.dropout(attn_weights, p=dropout, training=module.training)
|
| 192 |
+
|
| 193 |
+
attn_output = torch.matmul(attn_weights, value)
|
| 194 |
+
attn_output = attn_output.transpose(1, 2).contiguous()
|
| 195 |
+
|
| 196 |
+
return attn_output, attn_weights
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
class Ovis2VisionAttention(nn.Module):
|
| 200 |
+
"""Multi-headed attention from 'Attention Is All You Need' paper"""
|
| 201 |
+
|
| 202 |
+
def __init__(self, config):
|
| 203 |
+
super().__init__()
|
| 204 |
+
self.config = config
|
| 205 |
+
self.embed_dim = config.hidden_size
|
| 206 |
+
self.num_heads = config.num_attention_heads
|
| 207 |
+
self.head_dim = self.embed_dim // self.num_heads
|
| 208 |
+
if self.head_dim * self.num_heads != self.embed_dim:
|
| 209 |
+
raise ValueError(
|
| 210 |
+
f"embed_dim must be divisible by num_heads (got `embed_dim`: {self.embed_dim} and `num_heads`:"
|
| 211 |
+
f" {self.num_heads})."
|
| 212 |
+
)
|
| 213 |
+
self.scale = self.head_dim**-0.5
|
| 214 |
+
self.dropout = config.attention_dropout
|
| 215 |
+
self.is_causal = False
|
| 216 |
+
self.k_proj = nn.Linear(self.embed_dim, self.embed_dim, bias=config.qkv_bias)
|
| 217 |
+
self.v_proj = nn.Linear(self.embed_dim, self.embed_dim, bias=config.qkv_bias)
|
| 218 |
+
self.q_proj = nn.Linear(self.embed_dim, self.embed_dim, bias=config.qkv_bias)
|
| 219 |
+
self.out_proj = nn.Linear(self.embed_dim, self.embed_dim, bias=config.qkv_bias)
|
| 220 |
+
|
| 221 |
+
def forward(
|
| 222 |
+
self,
|
| 223 |
+
hidden_states: torch.Tensor,
|
| 224 |
+
attention_mask: torch.Tensor | None = None,
|
| 225 |
+
**kwargs,
|
| 226 |
+
) -> tuple[torch.Tensor, torch.Tensor | None]:
|
| 227 |
+
"""Input shape: Batch x Time x Channel"""
|
| 228 |
+
|
| 229 |
+
input_shape = hidden_states.shape[:-1]
|
| 230 |
+
|
| 231 |
+
hidden_shape = (*input_shape, -1, self.head_dim)
|
| 232 |
+
queries = self.q_proj(hidden_states).view(hidden_shape).transpose(1, 2)
|
| 233 |
+
keys = self.k_proj(hidden_states).view(hidden_shape).transpose(1, 2)
|
| 234 |
+
values = self.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)
|
| 235 |
+
|
| 236 |
+
attention_interface: Callable = ALL_ATTENTION_FUNCTIONS.get_interface(
|
| 237 |
+
self.config._attn_implementation, eager_attention_forward
|
| 238 |
+
)
|
| 239 |
+
|
| 240 |
+
attn_output, attn_weights = attention_interface(
|
| 241 |
+
self,
|
| 242 |
+
queries,
|
| 243 |
+
keys,
|
| 244 |
+
values,
|
| 245 |
+
attention_mask,
|
| 246 |
+
is_causal=self.is_causal,
|
| 247 |
+
scaling=self.scale,
|
| 248 |
+
dropout=0.0 if not self.training else self.dropout,
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
attn_output = attn_output.reshape(*input_shape, -1).contiguous()
|
| 252 |
+
attn_output = self.out_proj(attn_output)
|
| 253 |
+
|
| 254 |
+
return attn_output, attn_weights
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
class Ovis2MLP(nn.Module):
|
| 258 |
+
def __init__(self, config):
|
| 259 |
+
super().__init__()
|
| 260 |
+
self.config = config
|
| 261 |
+
self.hidden_size = config.hidden_size
|
| 262 |
+
self.intermediate_size = config.intermediate_size
|
| 263 |
+
self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=config.mlp_bias)
|
| 264 |
+
self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=config.mlp_bias)
|
| 265 |
+
self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=config.mlp_bias)
|
| 266 |
+
self.act_fn = ACT2FN[config.hidden_act]
|
| 267 |
+
|
| 268 |
+
def forward(self, x):
|
| 269 |
+
down_proj = self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))
|
| 270 |
+
return down_proj
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
class Ovis2VisionEncoderLayer(GradientCheckpointingLayer):
|
| 274 |
+
def __init__(self, config: Ovis2VisionConfig):
|
| 275 |
+
super().__init__()
|
| 276 |
+
self.attention = Ovis2VisionAttention(config)
|
| 277 |
+
self.ffn = Ovis2MLP(config)
|
| 278 |
+
self.rms_norm1 = Ovis2RMSNorm(config.hidden_size, config.rms_norm_eps)
|
| 279 |
+
self.rms_norm2 = Ovis2RMSNorm(config.hidden_size, config.rms_norm_eps)
|
| 280 |
+
|
| 281 |
+
def forward(
|
| 282 |
+
self,
|
| 283 |
+
hidden_states: torch.Tensor,
|
| 284 |
+
attention_mask: torch.Tensor | None = None,
|
| 285 |
+
**kwargs: Unpack[TransformersKwargs],
|
| 286 |
+
) -> torch.Tensor:
|
| 287 |
+
norm_hidden_states = self.rms_norm1(hidden_states)
|
| 288 |
+
attn_output, _ = self.attention(hidden_states=norm_hidden_states, attention_mask=attention_mask, **kwargs)
|
| 289 |
+
|
| 290 |
+
hidden_states = hidden_states + attn_output
|
| 291 |
+
norm_hidden_states = self.rms_norm2(hidden_states)
|
| 292 |
+
mlp_output = self.ffn(norm_hidden_states)
|
| 293 |
+
|
| 294 |
+
hidden_states = hidden_states + mlp_output
|
| 295 |
+
return hidden_states
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
class Ovis2VisionEncoder(nn.Module):
|
| 299 |
+
"""
|
| 300 |
+
Transformer encoder consisting of `config.num_hidden_layers` self attention layers. Each layer is a
|
| 301 |
+
[`Ovis2VisionEncoderLayer`].
|
| 302 |
+
|
| 303 |
+
Args:
|
| 304 |
+
config: Ovis2VisionConfig
|
| 305 |
+
"""
|
| 306 |
+
|
| 307 |
+
def __init__(self, config: Ovis2VisionConfig):
|
| 308 |
+
super().__init__()
|
| 309 |
+
self.config = config
|
| 310 |
+
self.layers = nn.ModuleList([Ovis2VisionEncoderLayer(config) for _ in range(config.num_hidden_layers)])
|
| 311 |
+
self.gradient_checkpointing = False
|
| 312 |
+
|
| 313 |
+
# Ignore copy
|
| 314 |
+
@can_return_tuple
|
| 315 |
+
@auto_docstring
|
| 316 |
+
def forward(
|
| 317 |
+
self,
|
| 318 |
+
inputs_embeds,
|
| 319 |
+
attention_mask: torch.Tensor | None = None,
|
| 320 |
+
**kwargs: Unpack[TransformersKwargs],
|
| 321 |
+
) -> BaseModelOutput:
|
| 322 |
+
hidden_states = inputs_embeds
|
| 323 |
+
for encoder_layer in self.layers:
|
| 324 |
+
hidden_states = encoder_layer(hidden_states, attention_mask, **kwargs)
|
| 325 |
+
|
| 326 |
+
return BaseModelOutput(last_hidden_state=hidden_states)
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
class Ovis2VisionTransformer(nn.Module):
|
| 330 |
+
def __init__(self, config: Ovis2VisionConfig):
|
| 331 |
+
super().__init__()
|
| 332 |
+
self.config = config
|
| 333 |
+
self.embeddings = Ovis2VisionEmbeddings(config)
|
| 334 |
+
self.encoder = Ovis2VisionEncoder(config)
|
| 335 |
+
self.rms_norm = Ovis2RMSNorm(config.hidden_size, config.rms_norm_eps)
|
| 336 |
+
self.gradient_checkpointing = False
|
| 337 |
+
|
| 338 |
+
@can_return_tuple
|
| 339 |
+
def forward(
|
| 340 |
+
self,
|
| 341 |
+
pixel_values,
|
| 342 |
+
attention_mask: torch.Tensor | None = None,
|
| 343 |
+
**kwargs,
|
| 344 |
+
):
|
| 345 |
+
hidden_states = self.embeddings(pixel_values)
|
| 346 |
+
|
| 347 |
+
encoder_outputs: BaseModelOutput = self.encoder(
|
| 348 |
+
inputs_embeds=hidden_states,
|
| 349 |
+
attention_mask=attention_mask,
|
| 350 |
+
**kwargs,
|
| 351 |
+
)
|
| 352 |
+
|
| 353 |
+
last_hidden_state = encoder_outputs.last_hidden_state
|
| 354 |
+
last_hidden_state = self.rms_norm(last_hidden_state)
|
| 355 |
+
|
| 356 |
+
return BaseModelOutput(last_hidden_state=last_hidden_state)
|
| 357 |
+
|
| 358 |
+
|
| 359 |
+
class Ovis2VisualEmbeddingTable(nn.Embedding):
|
| 360 |
+
def forward(self, visual_tokens: torch.Tensor) -> torch.Tensor:
|
| 361 |
+
if visual_tokens.dtype in [torch.int8, torch.int16, torch.int32, torch.int64, torch.long]:
|
| 362 |
+
return super().forward(visual_tokens)
|
| 363 |
+
return torch.matmul(visual_tokens, self.weight)
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
class Ovis2PreTrainedModel(PreTrainedModel):
|
| 367 |
+
config: Ovis2Config
|
| 368 |
+
base_model_prefix = "model"
|
| 369 |
+
input_modalities = ("image", "text")
|
| 370 |
+
supports_gradient_checkpointing = True
|
| 371 |
+
_no_split_modules = ["Ovis2VisionAttention"]
|
| 372 |
+
_skip_keys_device_placement = ["past_key_values"]
|
| 373 |
+
_supports_cache_class = True
|
| 374 |
+
_supports_flash_attn = True
|
| 375 |
+
_supports_flex_attn = True
|
| 376 |
+
_supports_sdpa = True
|
| 377 |
+
|
| 378 |
+
_can_compile_fullgraph = True
|
| 379 |
+
_supports_attention_backend = True
|
| 380 |
+
|
| 381 |
+
def _init_weights(self, module):
|
| 382 |
+
super()._init_weights(module)
|
| 383 |
+
if isinstance(module, Ovis2VisionEmbeddings):
|
| 384 |
+
init.copy_(module.position_ids, torch.arange(module.position_ids.shape[-1]).expand((1, -1)))
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
def hard_softmax(logits: torch.Tensor, dim: int):
|
| 388 |
+
y_soft = logits.softmax(dim)
|
| 389 |
+
# Straight through.
|
| 390 |
+
index = y_soft.max(dim, keepdim=True)[1]
|
| 391 |
+
y_hard = torch.zeros_like(logits, memory_format=torch.legacy_contiguous_format).scatter_(dim, index, 1.0)
|
| 392 |
+
ret = y_hard - y_soft.detach() + y_soft
|
| 393 |
+
|
| 394 |
+
return ret
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
class Ovis2VisionModel(Ovis2PreTrainedModel):
|
| 398 |
+
config: Ovis2VisionConfig
|
| 399 |
+
_can_record_outputs = {
|
| 400 |
+
"hidden_states": Ovis2VisionEncoderLayer,
|
| 401 |
+
"attentions": Ovis2VisionAttention,
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
def __init__(self, config: Ovis2VisionConfig):
|
| 405 |
+
super().__init__(config)
|
| 406 |
+
self.config = config
|
| 407 |
+
self.transformer = Ovis2VisionTransformer(config)
|
| 408 |
+
self.num_visual_indicator_tokens = config.num_visual_indicator_tokens
|
| 409 |
+
self.vocab_size = config.vocab_size
|
| 410 |
+
self.head_linear = nn.Linear(
|
| 411 |
+
config.hidden_size * config.hidden_stride * config.hidden_stride,
|
| 412 |
+
self.vocab_size - self.num_visual_indicator_tokens,
|
| 413 |
+
bias=False,
|
| 414 |
+
)
|
| 415 |
+
self.head_norm = nn.LayerNorm(self.vocab_size - self.num_visual_indicator_tokens)
|
| 416 |
+
|
| 417 |
+
self.post_init()
|
| 418 |
+
|
| 419 |
+
@merge_with_config_defaults
|
| 420 |
+
@capture_outputs
|
| 421 |
+
def forward(
|
| 422 |
+
self, pixel_values: torch.FloatTensor, **kwargs: Unpack[TransformersKwargs]
|
| 423 |
+
) -> tuple | BaseModelOutputWithVisualIndicatorFeatures:
|
| 424 |
+
outputs = self.transformer(pixel_values, **kwargs)
|
| 425 |
+
last_hidden_state = outputs[0]
|
| 426 |
+
if self.config.hidden_stride > 1:
|
| 427 |
+
num_images, seq_len, hidden_dim = last_hidden_state.shape
|
| 428 |
+
hidden_stride = self.config.hidden_stride
|
| 429 |
+
|
| 430 |
+
sqrt_l = int(math.sqrt(seq_len))
|
| 431 |
+
if sqrt_l * sqrt_l != seq_len:
|
| 432 |
+
raise ValueError("Token sequence length must be a perfect square")
|
| 433 |
+
|
| 434 |
+
pad_size = (hidden_stride - (sqrt_l % hidden_stride)) % hidden_stride
|
| 435 |
+
last_hidden_state = nn.functional.pad(last_hidden_state, (0, 0, 0, pad_size, 0, pad_size), "constant", 0)
|
| 436 |
+
sqrt_l += pad_size
|
| 437 |
+
|
| 438 |
+
last_hidden_state = last_hidden_state.reshape(
|
| 439 |
+
num_images, sqrt_l // hidden_stride, hidden_stride, sqrt_l // hidden_stride, hidden_stride, hidden_dim
|
| 440 |
+
)
|
| 441 |
+
last_hidden_state = last_hidden_state.permute(0, 1, 3, 2, 4, 5)
|
| 442 |
+
last_hidden_state = last_hidden_state.reshape(
|
| 443 |
+
num_images, -1, hidden_stride * hidden_stride * hidden_dim
|
| 444 |
+
) # (n, (sqrt_l//hs)^2, hs^2*d)
|
| 445 |
+
|
| 446 |
+
logits = self.head_linear(last_hidden_state)
|
| 447 |
+
logits = self.head_norm(logits)
|
| 448 |
+
|
| 449 |
+
if self.config.tokenize_function == "gumbel_argmax":
|
| 450 |
+
prob_token = nn.functional.gumbel_softmax(logits, dim=-1, hard=True)
|
| 451 |
+
elif self.config.tokenize_function == "st_argmax":
|
| 452 |
+
prob_token = hard_softmax(logits, dim=-1)
|
| 453 |
+
elif self.config.tokenize_function == "softmax":
|
| 454 |
+
prob_token = nn.functional.softmax(logits, dim=-1)
|
| 455 |
+
|
| 456 |
+
return BaseModelOutputWithVisualIndicatorFeatures(
|
| 457 |
+
last_hidden_state=last_hidden_state,
|
| 458 |
+
pooler_output=prob_token,
|
| 459 |
+
)
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
@auto_docstring(
|
| 463 |
+
custom_intro="""
|
| 464 |
+
The Ovis2 model which consists of a vision backbone and a language model, without a language modeling head.
|
| 465 |
+
"""
|
| 466 |
+
)
|
| 467 |
+
class Ovis2Model(Ovis2PreTrainedModel):
|
| 468 |
+
def __init__(self, config: Ovis2Config):
|
| 469 |
+
super().__init__(config)
|
| 470 |
+
self.vision_tower = Ovis2VisionModel(config.vision_config)
|
| 471 |
+
self.language_model = AutoModel.from_config(config.text_config)
|
| 472 |
+
self.visual_embeddings_table = Ovis2VisualEmbeddingTable(config.vision_config.vocab_size, config.hidden_size)
|
| 473 |
+
|
| 474 |
+
self.visual_vocab_size = config.vision_config.vocab_size
|
| 475 |
+
self.vocab_size = config.vocab_size
|
| 476 |
+
self.visual_indicator_token_ids = config.visual_indicator_token_ids
|
| 477 |
+
self.post_init()
|
| 478 |
+
|
| 479 |
+
@can_return_tuple
|
| 480 |
+
@auto_docstring(
|
| 481 |
+
custom_intro="Obtains image last hidden states from the vision tower and apply multimodal projection."
|
| 482 |
+
)
|
| 483 |
+
def get_image_features(
|
| 484 |
+
self,
|
| 485 |
+
pixel_values: torch.FloatTensor,
|
| 486 |
+
**kwargs: Unpack[TransformersKwargs],
|
| 487 |
+
) -> tuple | BaseModelOutputWithVisualIndicatorFeatures:
|
| 488 |
+
image_outputs = self.vision_tower(pixel_values, return_dict=True, **kwargs)
|
| 489 |
+
image_features = image_outputs.pooler_output
|
| 490 |
+
batch_size, img_seq_len, _ = image_features.shape
|
| 491 |
+
padding_tensor = torch.zeros(
|
| 492 |
+
(batch_size, img_seq_len, self.vision_tower.num_visual_indicator_tokens),
|
| 493 |
+
dtype=image_features.dtype,
|
| 494 |
+
device=image_features.device,
|
| 495 |
+
requires_grad=False,
|
| 496 |
+
layout=image_features.layout,
|
| 497 |
+
)
|
| 498 |
+
image_features = torch.cat([image_features, padding_tensor], dim=2)
|
| 499 |
+
image_features = self.visual_embeddings_table(image_features)
|
| 500 |
+
|
| 501 |
+
visual_indicator = torch.arange(
|
| 502 |
+
self.visual_vocab_size - self.vision_tower.num_visual_indicator_tokens,
|
| 503 |
+
self.visual_vocab_size,
|
| 504 |
+
dtype=torch.long,
|
| 505 |
+
).to(image_features.device)
|
| 506 |
+
image_outputs.pooler_output = image_features
|
| 507 |
+
image_outputs.visual_indicator_features = self.visual_embeddings_table(visual_indicator)
|
| 508 |
+
|
| 509 |
+
return image_outputs
|
| 510 |
+
|
| 511 |
+
def get_placeholder_mask(
|
| 512 |
+
self, input_ids: torch.LongTensor, inputs_embeds: torch.FloatTensor, image_features: torch.FloatTensor
|
| 513 |
+
):
|
| 514 |
+
"""
|
| 515 |
+
Obtains multimodal placeholder mask from `input_ids` or `inputs_embeds`, and checks that the placeholder token count is
|
| 516 |
+
equal to the length of multimodal features. If the lengths are different, an error is raised.
|
| 517 |
+
"""
|
| 518 |
+
if input_ids is None:
|
| 519 |
+
special_image_mask = inputs_embeds == self.get_input_embeddings()(
|
| 520 |
+
torch.tensor(self.config.image_token_id, dtype=torch.long, device=inputs_embeds.device)
|
| 521 |
+
)
|
| 522 |
+
special_image_mask = special_image_mask.all(-1)
|
| 523 |
+
else:
|
| 524 |
+
special_image_mask = input_ids == self.config.image_token_id
|
| 525 |
+
|
| 526 |
+
n_image_tokens = special_image_mask.sum()
|
| 527 |
+
n_image_features = image_features.shape[0] * image_features.shape[1]
|
| 528 |
+
special_image_mask = special_image_mask.unsqueeze(-1).expand_as(inputs_embeds).to(inputs_embeds.device)
|
| 529 |
+
torch_compilable_check(
|
| 530 |
+
inputs_embeds[special_image_mask].numel() == image_features.numel(),
|
| 531 |
+
f"Image features and image tokens do not match, tokens: {n_image_tokens}, features: {n_image_features}",
|
| 532 |
+
)
|
| 533 |
+
return special_image_mask
|
| 534 |
+
|
| 535 |
+
@can_return_tuple
|
| 536 |
+
@auto_docstring
|
| 537 |
+
def forward(
|
| 538 |
+
self,
|
| 539 |
+
input_ids: torch.LongTensor | None = None,
|
| 540 |
+
pixel_values: torch.FloatTensor | None = None,
|
| 541 |
+
attention_mask: torch.Tensor | None = None,
|
| 542 |
+
position_ids: torch.LongTensor | None = None,
|
| 543 |
+
past_key_values: Cache | None = None,
|
| 544 |
+
inputs_embeds: torch.FloatTensor | None = None,
|
| 545 |
+
labels: torch.LongTensor | None = None,
|
| 546 |
+
use_cache: bool | None = None,
|
| 547 |
+
logits_to_keep: int | torch.Tensor = 0,
|
| 548 |
+
**kwargs,
|
| 549 |
+
) -> tuple | Ovis2ModelOutputWithPast:
|
| 550 |
+
if (input_ids is None) ^ (inputs_embeds is not None):
|
| 551 |
+
raise ValueError("You must specify exactly one of input_ids or inputs_embeds")
|
| 552 |
+
|
| 553 |
+
if inputs_embeds is None:
|
| 554 |
+
inputs_embeds = self.get_input_embeddings()(input_ids)
|
| 555 |
+
|
| 556 |
+
if pixel_values is not None:
|
| 557 |
+
image_outputs = self.get_image_features(pixel_values=pixel_values, return_dict=True)
|
| 558 |
+
image_features = image_outputs.pooler_output
|
| 559 |
+
visual_indicator_features = image_outputs.visual_indicator_features
|
| 560 |
+
|
| 561 |
+
special_image_mask = self.get_placeholder_mask(
|
| 562 |
+
input_ids,
|
| 563 |
+
inputs_embeds=inputs_embeds,
|
| 564 |
+
image_features=image_features,
|
| 565 |
+
)
|
| 566 |
+
inputs_embeds = inputs_embeds.masked_scatter(special_image_mask, image_features)
|
| 567 |
+
|
| 568 |
+
for i, visual_indicator_id in enumerate(self.visual_indicator_token_ids):
|
| 569 |
+
if input_ids is None:
|
| 570 |
+
mask = inputs_embeds == self.get_input_embeddings()(
|
| 571 |
+
torch.tensor(visual_indicator_id, dtype=torch.long, device=inputs_embeds.device)
|
| 572 |
+
)
|
| 573 |
+
mask = mask.all(-1)
|
| 574 |
+
else:
|
| 575 |
+
mask = (input_ids == visual_indicator_id).to(inputs_embeds.device)
|
| 576 |
+
|
| 577 |
+
if mask.any():
|
| 578 |
+
inputs_embeds[mask] = (
|
| 579 |
+
visual_indicator_features[i]
|
| 580 |
+
.expand_as(inputs_embeds[mask])
|
| 581 |
+
.to(inputs_embeds.device, inputs_embeds.dtype)
|
| 582 |
+
)
|
| 583 |
+
|
| 584 |
+
outputs = self.language_model(
|
| 585 |
+
attention_mask=attention_mask,
|
| 586 |
+
position_ids=position_ids,
|
| 587 |
+
past_key_values=past_key_values,
|
| 588 |
+
inputs_embeds=inputs_embeds,
|
| 589 |
+
use_cache=use_cache,
|
| 590 |
+
logits_to_keep=logits_to_keep,
|
| 591 |
+
**kwargs,
|
| 592 |
+
)
|
| 593 |
+
|
| 594 |
+
return Ovis2ModelOutputWithPast(
|
| 595 |
+
last_hidden_state=outputs.last_hidden_state,
|
| 596 |
+
past_key_values=outputs.past_key_values,
|
| 597 |
+
hidden_states=outputs.hidden_states,
|
| 598 |
+
attentions=outputs.attentions,
|
| 599 |
+
image_hidden_states=image_features if pixel_values is not None else None,
|
| 600 |
+
)
|
| 601 |
+
|
| 602 |
+
|
| 603 |
+
@auto_docstring
|
| 604 |
+
class Ovis2ForConditionalGeneration(Ovis2PreTrainedModel, GenerationMixin):
|
| 605 |
+
_tied_weights_keys = {"lm_head.weight": "model.language_model.embed_tokens.weight"}
|
| 606 |
+
|
| 607 |
+
def __init__(self, config: Ovis2Config):
|
| 608 |
+
super().__init__(config)
|
| 609 |
+
self.model = Ovis2Model(config)
|
| 610 |
+
self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
|
| 611 |
+
self.post_init()
|
| 612 |
+
|
| 613 |
+
def get_output_embeddings(self) -> nn.Module:
|
| 614 |
+
return self.lm_head
|
| 615 |
+
|
| 616 |
+
@auto_docstring
|
| 617 |
+
def get_image_features(
|
| 618 |
+
self, pixel_values: torch.FloatTensor, **kwargs: Unpack[TransformersKwargs]
|
| 619 |
+
) -> tuple | BaseModelOutputWithVisualIndicatorFeatures:
|
| 620 |
+
return self.model.get_image_features(pixel_values=pixel_values, **kwargs)
|
| 621 |
+
|
| 622 |
+
@can_return_tuple
|
| 623 |
+
@auto_docstring
|
| 624 |
+
def forward(
|
| 625 |
+
self,
|
| 626 |
+
input_ids: torch.LongTensor | None = None,
|
| 627 |
+
pixel_values: torch.FloatTensor | None = None,
|
| 628 |
+
attention_mask: torch.Tensor | None = None,
|
| 629 |
+
position_ids: torch.LongTensor | None = None,
|
| 630 |
+
past_key_values: Cache | None = None,
|
| 631 |
+
inputs_embeds: torch.FloatTensor | None = None,
|
| 632 |
+
labels: torch.LongTensor | None = None,
|
| 633 |
+
use_cache: bool | None = None,
|
| 634 |
+
logits_to_keep: int | torch.Tensor = 0,
|
| 635 |
+
**kwargs,
|
| 636 |
+
) -> tuple | Ovis2CausalLMOutputWithPast:
|
| 637 |
+
r"""
|
| 638 |
+
labels (`torch.LongTensor` of shape `(batch_size, sequence_length)`, *optional*):
|
| 639 |
+
Labels for computing the masked language modeling loss. Indices should either be in `[0, ...,
|
| 640 |
+
config.vocab_size]` or -100 (see `input_ids` docstring). Tokens with indices set to `-100` are ignored
|
| 641 |
+
(masked), the loss is only computed for the tokens with labels in `[0, ..., config.vocab_size]`.
|
| 642 |
+
|
| 643 |
+
Example:
|
| 644 |
+
|
| 645 |
+
```python
|
| 646 |
+
>>> from PIL import Image
|
| 647 |
+
>>> import httpx
|
| 648 |
+
>>> from io import BytesIO
|
| 649 |
+
>>> from transformers import AutoProcessor, Ovis2ForConditionalGeneration
|
| 650 |
+
|
| 651 |
+
>>> model = Ovis2ForConditionalGeneration.from_pretrained("thisisiron/Ovis2-2B-hf")
|
| 652 |
+
>>> processor = AutoProcessor.from_pretrained("thisisiron/Ovis2-2B-hf")
|
| 653 |
+
|
| 654 |
+
>>> prompt = "<|im_start|>user\n<image>\nDescribe the image.<|im_end|>\n<|im_start|>assistant\n"
|
| 655 |
+
>>> url = "http://images.cocodataset.org/val2014/COCO_val2014_000000537955.jpg"
|
| 656 |
+
>>> with httpx.stream("GET", url) as response:
|
| 657 |
+
... image = Image.open(BytesIO(response.read()))
|
| 658 |
+
|
| 659 |
+
>>> inputs = processor(images=image, text=prompt, return_tensors="pt")
|
| 660 |
+
|
| 661 |
+
>>> # Generate
|
| 662 |
+
>>> generate_ids = model.generate(**inputs, max_new_tokens=15)
|
| 663 |
+
>>> processor.batch_decode(generate_ids, skip_special_tokens=True)[0]
|
| 664 |
+
"user\n\nDescribe the image.\nassistant\nThe image features a brown dog standing on a wooden floor, looking up with"
|
| 665 |
+
```"""
|
| 666 |
+
outputs = self.model(
|
| 667 |
+
input_ids=input_ids,
|
| 668 |
+
pixel_values=pixel_values,
|
| 669 |
+
attention_mask=attention_mask,
|
| 670 |
+
position_ids=position_ids,
|
| 671 |
+
past_key_values=past_key_values,
|
| 672 |
+
inputs_embeds=inputs_embeds,
|
| 673 |
+
use_cache=use_cache,
|
| 674 |
+
**kwargs,
|
| 675 |
+
)
|
| 676 |
+
|
| 677 |
+
hidden_states = outputs[0]
|
| 678 |
+
# Only compute necessary logits, and do not upcast them to float if we are not computing the loss
|
| 679 |
+
slice_indices = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep
|
| 680 |
+
logits = self.lm_head(hidden_states[:, slice_indices, :])
|
| 681 |
+
|
| 682 |
+
loss = None
|
| 683 |
+
if labels is not None:
|
| 684 |
+
loss = self.loss_function(
|
| 685 |
+
logits=logits, labels=labels, vocab_size=self.config.text_config.vocab_size, **kwargs
|
| 686 |
+
)
|
| 687 |
+
|
| 688 |
+
return Ovis2CausalLMOutputWithPast(
|
| 689 |
+
loss=loss,
|
| 690 |
+
logits=logits,
|
| 691 |
+
past_key_values=outputs.past_key_values,
|
| 692 |
+
hidden_states=outputs.hidden_states,
|
| 693 |
+
attentions=outputs.attentions,
|
| 694 |
+
image_hidden_states=outputs.image_hidden_states,
|
| 695 |
+
)
|
| 696 |
+
|
| 697 |
+
def prepare_inputs_for_generation(
|
| 698 |
+
self,
|
| 699 |
+
input_ids,
|
| 700 |
+
past_key_values=None,
|
| 701 |
+
inputs_embeds=None,
|
| 702 |
+
pixel_values=None,
|
| 703 |
+
attention_mask=None,
|
| 704 |
+
logits_to_keep=None,
|
| 705 |
+
is_first_iteration=False,
|
| 706 |
+
**kwargs,
|
| 707 |
+
):
|
| 708 |
+
# Overwritten -- in specific circumstances we don't want to forward image inputs to the model
|
| 709 |
+
|
| 710 |
+
model_inputs = super().prepare_inputs_for_generation(
|
| 711 |
+
input_ids,
|
| 712 |
+
past_key_values=past_key_values,
|
| 713 |
+
inputs_embeds=inputs_embeds,
|
| 714 |
+
attention_mask=attention_mask,
|
| 715 |
+
logits_to_keep=logits_to_keep,
|
| 716 |
+
is_first_iteration=is_first_iteration,
|
| 717 |
+
**kwargs,
|
| 718 |
+
)
|
| 719 |
+
|
| 720 |
+
if is_first_iteration or not kwargs.get("use_cache", True):
|
| 721 |
+
# Pixel values are used only in the first iteration if available
|
| 722 |
+
# In subsequent iterations, they are already merged with text and cached
|
| 723 |
+
# NOTE: first iteration doesn't have to be prefill, it can be the first
|
| 724 |
+
# iteration with a question and cached system prompt (continue generate from cache)
|
| 725 |
+
model_inputs["pixel_values"] = pixel_values
|
| 726 |
+
|
| 727 |
+
return model_inputs
|
| 728 |
+
|
| 729 |
+
|
| 730 |
+
__all__ = ["Ovis2PreTrainedModel", "Ovis2Model", "Ovis2ForConditionalGeneration"]
|
LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/textnet/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 The HuggingFace Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
from typing import TYPE_CHECKING
|
| 15 |
+
|
| 16 |
+
from ...utils import _LazyModule
|
| 17 |
+
from ...utils.import_utils import define_import_structure
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
if TYPE_CHECKING:
|
| 21 |
+
from .configuration_textnet import *
|
| 22 |
+
from .image_processing_pil_textnet import *
|
| 23 |
+
from .image_processing_textnet import *
|
| 24 |
+
from .modeling_textnet import *
|
| 25 |
+
else:
|
| 26 |
+
import sys
|
| 27 |
+
|
| 28 |
+
_file = globals()["__file__"]
|
| 29 |
+
sys.modules[__name__] = _LazyModule(__name__, _file, define_import_structure(_file), module_spec=__spec__)
|
LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/textnet/configuration_textnet.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 the Fast authors and HuggingFace Inc. team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
"""TextNet model configuration"""
|
| 15 |
+
|
| 16 |
+
from huggingface_hub.dataclasses import strict
|
| 17 |
+
|
| 18 |
+
from ...backbone_utils import BackboneConfigMixin
|
| 19 |
+
from ...configuration_utils import PreTrainedConfig
|
| 20 |
+
from ...utils import auto_docstring
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
@auto_docstring(checkpoint="czczup/textnet-base")
|
| 24 |
+
@strict
|
| 25 |
+
class TextNetConfig(BackboneConfigMixin, PreTrainedConfig):
|
| 26 |
+
r"""
|
| 27 |
+
stem_kernel_size (`int`, *optional*, defaults to 3):
|
| 28 |
+
The kernel size for the initial convolution layer.
|
| 29 |
+
stem_stride (`int`, *optional*, defaults to 2):
|
| 30 |
+
The stride for the initial convolution layer.
|
| 31 |
+
stem_num_channels (`int`, *optional*, defaults to 3):
|
| 32 |
+
The num of channels in input for the initial convolution layer.
|
| 33 |
+
stem_out_channels (`int`, *optional*, defaults to 64):
|
| 34 |
+
The num of channels in out for the initial convolution layer.
|
| 35 |
+
stem_act_func (`str`, *optional*, defaults to `"relu"`):
|
| 36 |
+
The activation function for the initial convolution layer.
|
| 37 |
+
conv_layer_kernel_sizes (`list[list[list[int]]]`, *optional*):
|
| 38 |
+
A list of stage-wise kernel sizes. If `None`, defaults to:
|
| 39 |
+
`[[[3, 3], [3, 3], [3, 3]], [[3, 3], [1, 3], [3, 3], [3, 1]], [[3, 3], [3, 3], [3, 1], [1, 3]], [[3, 3], [3, 1], [1, 3], [3, 3]]]`.
|
| 40 |
+
conv_layer_strides (`list[list[int]]`, *optional*):
|
| 41 |
+
A list of stage-wise strides. If `None`, defaults to:
|
| 42 |
+
`[[1, 2, 1], [2, 1, 1, 1], [2, 1, 1, 1], [2, 1, 1, 1]]`.
|
| 43 |
+
|
| 44 |
+
Examples:
|
| 45 |
+
|
| 46 |
+
```python
|
| 47 |
+
>>> from transformers import TextNetConfig, TextNetBackbone
|
| 48 |
+
|
| 49 |
+
>>> # Initializing a TextNetConfig
|
| 50 |
+
>>> configuration = TextNetConfig()
|
| 51 |
+
|
| 52 |
+
>>> # Initializing a model (with random weights)
|
| 53 |
+
>>> model = TextNetBackbone(configuration)
|
| 54 |
+
|
| 55 |
+
>>> # Accessing the model configuration
|
| 56 |
+
>>> configuration = model.config
|
| 57 |
+
```"""
|
| 58 |
+
|
| 59 |
+
model_type = "textnet"
|
| 60 |
+
|
| 61 |
+
stem_kernel_size: int = 3
|
| 62 |
+
stem_stride: int = 2
|
| 63 |
+
stem_num_channels: int = 3
|
| 64 |
+
stem_out_channels: int = 64
|
| 65 |
+
stem_act_func: str = "relu"
|
| 66 |
+
image_size: list[int] | tuple[int, int] | int = (640, 640)
|
| 67 |
+
conv_layer_kernel_sizes: list | None = None
|
| 68 |
+
conv_layer_strides: list | None = None
|
| 69 |
+
hidden_sizes: list[int] | tuple[int, ...] = (64, 64, 128, 256, 512)
|
| 70 |
+
batch_norm_eps: float = 1e-5
|
| 71 |
+
initializer_range: float = 0.02
|
| 72 |
+
_out_features: list[str] | None = None
|
| 73 |
+
_out_indices: list[int] | None = None
|
| 74 |
+
|
| 75 |
+
def __post_init__(self, **kwargs):
|
| 76 |
+
if self.conv_layer_kernel_sizes is None:
|
| 77 |
+
self.conv_layer_kernel_sizes = [
|
| 78 |
+
[[3, 3], [3, 3], [3, 3]],
|
| 79 |
+
[[3, 3], [1, 3], [3, 3], [3, 1]],
|
| 80 |
+
[[3, 3], [3, 3], [3, 1], [1, 3]],
|
| 81 |
+
[[3, 3], [3, 1], [1, 3], [3, 3]],
|
| 82 |
+
]
|
| 83 |
+
if self.conv_layer_strides is None:
|
| 84 |
+
self.conv_layer_strides = [[1, 2, 1], [2, 1, 1, 1], [2, 1, 1, 1], [2, 1, 1, 1]]
|
| 85 |
+
|
| 86 |
+
self.depths = [len(layer) for layer in self.conv_layer_kernel_sizes]
|
| 87 |
+
self.stage_names = ["stem"] + [f"stage{idx}" for idx in range(1, 5)]
|
| 88 |
+
self.set_output_features_output_indices(
|
| 89 |
+
out_indices=kwargs.pop("out_indices", None), out_features=kwargs.pop("out_features", None)
|
| 90 |
+
)
|
| 91 |
+
super().__post_init__(**kwargs)
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
__all__ = ["TextNetConfig"]
|
LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/textnet/image_processing_pil_textnet.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 the Fast authors and The HuggingFace Inc. team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
"""Image processor class for TextNet."""
|
| 15 |
+
|
| 16 |
+
import numpy as np
|
| 17 |
+
|
| 18 |
+
from ...image_processing_backends import PilBackend
|
| 19 |
+
from ...image_processing_utils import BatchFeature
|
| 20 |
+
from ...image_transforms import get_resize_output_image_size
|
| 21 |
+
from ...image_transforms import resize as np_resize
|
| 22 |
+
from ...image_utils import (
|
| 23 |
+
IMAGENET_DEFAULT_MEAN,
|
| 24 |
+
IMAGENET_DEFAULT_STD,
|
| 25 |
+
ChannelDimension,
|
| 26 |
+
ImageInput,
|
| 27 |
+
PILImageResampling,
|
| 28 |
+
SizeDict,
|
| 29 |
+
)
|
| 30 |
+
from ...processing_utils import ImagesKwargs, Unpack
|
| 31 |
+
from ...utils import TensorType, auto_docstring
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
# Adapted from transformers.models.textnet.image_processing_textnet.TextNetImageProcessorKwargs
|
| 35 |
+
class TextNetImageProcessorKwargs(ImagesKwargs, total=False):
|
| 36 |
+
"""
|
| 37 |
+
size_divisor (`int`, *optional*, defaults to `self.size_divisor`):
|
| 38 |
+
Ensures height and width are rounded to a multiple of this value after resizing.
|
| 39 |
+
"""
|
| 40 |
+
|
| 41 |
+
size_divisor: int
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
@auto_docstring
|
| 45 |
+
class TextNetImageProcessorPil(PilBackend):
|
| 46 |
+
"""PIL backend for TextNet with size_divisor resize."""
|
| 47 |
+
|
| 48 |
+
valid_kwargs = TextNetImageProcessorKwargs
|
| 49 |
+
|
| 50 |
+
resample = PILImageResampling.BILINEAR
|
| 51 |
+
image_mean = IMAGENET_DEFAULT_MEAN
|
| 52 |
+
image_std = IMAGENET_DEFAULT_STD
|
| 53 |
+
size = {"shortest_edge": 640}
|
| 54 |
+
default_to_square = False
|
| 55 |
+
crop_size = {"height": 224, "width": 224}
|
| 56 |
+
do_resize = True
|
| 57 |
+
do_center_crop = False
|
| 58 |
+
do_rescale = True
|
| 59 |
+
do_normalize = True
|
| 60 |
+
do_convert_rgb = True
|
| 61 |
+
size_divisor = 32
|
| 62 |
+
|
| 63 |
+
def __init__(self, **kwargs: Unpack[TextNetImageProcessorKwargs]):
|
| 64 |
+
super().__init__(**kwargs)
|
| 65 |
+
|
| 66 |
+
@auto_docstring
|
| 67 |
+
def preprocess(self, images: ImageInput, **kwargs: Unpack[TextNetImageProcessorKwargs]) -> BatchFeature:
|
| 68 |
+
return super().preprocess(images, **kwargs)
|
| 69 |
+
|
| 70 |
+
def resize(
|
| 71 |
+
self,
|
| 72 |
+
image: np.ndarray,
|
| 73 |
+
size: SizeDict,
|
| 74 |
+
resample: "PILImageResampling | None",
|
| 75 |
+
size_divisor: int = 32,
|
| 76 |
+
**kwargs,
|
| 77 |
+
) -> np.ndarray:
|
| 78 |
+
"""Resize to shortest_edge then round up to be divisible by size_divisor."""
|
| 79 |
+
if not size.shortest_edge:
|
| 80 |
+
raise ValueError(f"Size must contain 'shortest_edge' key. Got {size.keys()}")
|
| 81 |
+
height, width = get_resize_output_image_size(
|
| 82 |
+
image,
|
| 83 |
+
size=size.shortest_edge,
|
| 84 |
+
default_to_square=False,
|
| 85 |
+
input_data_format=ChannelDimension.FIRST,
|
| 86 |
+
)
|
| 87 |
+
# Round up to be divisible by size_divisor
|
| 88 |
+
if height % size_divisor != 0:
|
| 89 |
+
height += size_divisor - (height % size_divisor)
|
| 90 |
+
if width % size_divisor != 0:
|
| 91 |
+
width += size_divisor - (width % size_divisor)
|
| 92 |
+
return np_resize(
|
| 93 |
+
image,
|
| 94 |
+
size=(height, width),
|
| 95 |
+
resample=resample,
|
| 96 |
+
data_format=ChannelDimension.FIRST,
|
| 97 |
+
input_data_format=ChannelDimension.FIRST,
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
def _preprocess(
|
| 101 |
+
self,
|
| 102 |
+
images: list[np.ndarray],
|
| 103 |
+
do_resize: bool,
|
| 104 |
+
size: SizeDict,
|
| 105 |
+
resample: "PILImageResampling | None",
|
| 106 |
+
do_center_crop: bool,
|
| 107 |
+
crop_size: SizeDict,
|
| 108 |
+
do_rescale: bool,
|
| 109 |
+
rescale_factor: float,
|
| 110 |
+
do_normalize: bool,
|
| 111 |
+
image_mean: float | list[float] | None,
|
| 112 |
+
image_std: float | list[float] | None,
|
| 113 |
+
do_pad: bool | None,
|
| 114 |
+
pad_size: SizeDict | None,
|
| 115 |
+
return_tensors: str | TensorType | None,
|
| 116 |
+
size_divisor: int = 32,
|
| 117 |
+
**kwargs,
|
| 118 |
+
) -> BatchFeature:
|
| 119 |
+
"""Custom preprocessing for TextNet."""
|
| 120 |
+
processed_images = []
|
| 121 |
+
for image in images:
|
| 122 |
+
if do_resize:
|
| 123 |
+
image = self.resize(image, size, resample, size_divisor=size_divisor)
|
| 124 |
+
if do_center_crop:
|
| 125 |
+
image = self.center_crop(image, crop_size)
|
| 126 |
+
if do_rescale:
|
| 127 |
+
image = self.rescale(image, rescale_factor)
|
| 128 |
+
if do_normalize:
|
| 129 |
+
image = self.normalize(image, image_mean, image_std)
|
| 130 |
+
processed_images.append(image)
|
| 131 |
+
return BatchFeature(data={"pixel_values": processed_images}, tensor_type=return_tensors)
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
__all__ = ["TextNetImageProcessorPil"]
|
LTA_openwebtext_dualt/mini_owt_logdirichlet/.venv_qwen35_uv/lib/python3.12/site-packages/transformers/models/textnet/modeling_textnet.py
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 the Fast authors and The HuggingFace Inc. team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
"""PyTorch TextNet model."""
|
| 15 |
+
|
| 16 |
+
from typing import Any
|
| 17 |
+
|
| 18 |
+
import torch
|
| 19 |
+
import torch.nn as nn
|
| 20 |
+
from torch import Tensor
|
| 21 |
+
|
| 22 |
+
from ...activations import ACT2CLS
|
| 23 |
+
from ...backbone_utils import BackboneMixin, filter_output_hidden_states
|
| 24 |
+
from ...modeling_outputs import (
|
| 25 |
+
BackboneOutput,
|
| 26 |
+
BaseModelOutputWithNoAttention,
|
| 27 |
+
BaseModelOutputWithPoolingAndNoAttention,
|
| 28 |
+
ImageClassifierOutputWithNoAttention,
|
| 29 |
+
)
|
| 30 |
+
from ...modeling_utils import PreTrainedModel
|
| 31 |
+
from ...utils import auto_docstring, logging
|
| 32 |
+
from ...utils.generic import can_return_tuple
|
| 33 |
+
from .configuration_textnet import TextNetConfig
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
logger = logging.get_logger(__name__)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class TextNetConvLayer(nn.Module):
|
| 40 |
+
def __init__(self, config: TextNetConfig):
|
| 41 |
+
super().__init__()
|
| 42 |
+
|
| 43 |
+
self.kernel_size = config.stem_kernel_size
|
| 44 |
+
self.stride = config.stem_stride
|
| 45 |
+
self.activation_function = config.stem_act_func
|
| 46 |
+
|
| 47 |
+
padding = (
|
| 48 |
+
(config.kernel_size[0] // 2, config.kernel_size[1] // 2)
|
| 49 |
+
if isinstance(config.stem_kernel_size, tuple)
|
| 50 |
+
else config.stem_kernel_size // 2
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
self.conv = nn.Conv2d(
|
| 54 |
+
config.stem_num_channels,
|
| 55 |
+
config.stem_out_channels,
|
| 56 |
+
kernel_size=config.stem_kernel_size,
|
| 57 |
+
stride=config.stem_stride,
|
| 58 |
+
padding=padding,
|
| 59 |
+
bias=False,
|
| 60 |
+
)
|
| 61 |
+
self.batch_norm = nn.BatchNorm2d(config.stem_out_channels, config.batch_norm_eps)
|
| 62 |
+
|
| 63 |
+
self.activation = nn.Identity()
|
| 64 |
+
if self.activation_function is not None:
|
| 65 |
+
self.activation = ACT2CLS[self.activation_function]()
|
| 66 |
+
|
| 67 |
+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
| 68 |
+
hidden_states = self.conv(hidden_states)
|
| 69 |
+
hidden_states = self.batch_norm(hidden_states)
|
| 70 |
+
return self.activation(hidden_states)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
class TextNetRepConvLayer(nn.Module):
|
| 74 |
+
r"""
|
| 75 |
+
This layer supports re-parameterization by combining multiple convolutional branches
|
| 76 |
+
(e.g., main convolution, vertical, horizontal, and identity branches) during training.
|
| 77 |
+
At inference time, these branches can be collapsed into a single convolution for
|
| 78 |
+
efficiency, as per the re-parameterization paradigm.
|
| 79 |
+
|
| 80 |
+
The "Rep" in the name stands for "re-parameterization" (introduced by RepVGG).
|
| 81 |
+
"""
|
| 82 |
+
|
| 83 |
+
def __init__(self, config: TextNetConfig, in_channels: int, out_channels: int, kernel_size: int, stride: int):
|
| 84 |
+
super().__init__()
|
| 85 |
+
|
| 86 |
+
self.num_channels = in_channels
|
| 87 |
+
self.out_channels = out_channels
|
| 88 |
+
self.kernel_size = kernel_size
|
| 89 |
+
self.stride = stride
|
| 90 |
+
|
| 91 |
+
padding = ((kernel_size[0] - 1) // 2, (kernel_size[1] - 1) // 2)
|
| 92 |
+
|
| 93 |
+
self.activation_function = nn.ReLU()
|
| 94 |
+
|
| 95 |
+
self.main_conv = nn.Conv2d(
|
| 96 |
+
in_channels=in_channels,
|
| 97 |
+
out_channels=out_channels,
|
| 98 |
+
kernel_size=kernel_size,
|
| 99 |
+
stride=stride,
|
| 100 |
+
padding=padding,
|
| 101 |
+
bias=False,
|
| 102 |
+
)
|
| 103 |
+
self.main_batch_norm = nn.BatchNorm2d(num_features=out_channels, eps=config.batch_norm_eps)
|
| 104 |
+
|
| 105 |
+
vertical_padding = ((kernel_size[0] - 1) // 2, 0)
|
| 106 |
+
horizontal_padding = (0, (kernel_size[1] - 1) // 2)
|
| 107 |
+
|
| 108 |
+
if kernel_size[1] != 1:
|
| 109 |
+
self.vertical_conv = nn.Conv2d(
|
| 110 |
+
in_channels=in_channels,
|
| 111 |
+
out_channels=out_channels,
|
| 112 |
+
kernel_size=(kernel_size[0], 1),
|
| 113 |
+
stride=stride,
|
| 114 |
+
padding=vertical_padding,
|
| 115 |
+
bias=False,
|
| 116 |
+
)
|
| 117 |
+
self.vertical_batch_norm = nn.BatchNorm2d(num_features=out_channels, eps=config.batch_norm_eps)
|
| 118 |
+
else:
|
| 119 |
+
self.vertical_conv, self.vertical_batch_norm = None, None
|
| 120 |
+
|
| 121 |
+
if kernel_size[0] != 1:
|
| 122 |
+
self.horizontal_conv = nn.Conv2d(
|
| 123 |
+
in_channels=in_channels,
|
| 124 |
+
out_channels=out_channels,
|
| 125 |
+
kernel_size=(1, kernel_size[1]),
|
| 126 |
+
stride=stride,
|
| 127 |
+
padding=horizontal_padding,
|
| 128 |
+
bias=False,
|
| 129 |
+
)
|
| 130 |
+
self.horizontal_batch_norm = nn.BatchNorm2d(num_features=out_channels, eps=config.batch_norm_eps)
|
| 131 |
+
else:
|
| 132 |
+
self.horizontal_conv, self.horizontal_batch_norm = None, None
|
| 133 |
+
|
| 134 |
+
self.rbr_identity = (
|
| 135 |
+
nn.BatchNorm2d(num_features=in_channels, eps=config.batch_norm_eps)
|
| 136 |
+
if out_channels == in_channels and stride == 1
|
| 137 |
+
else None
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
| 141 |
+
main_outputs = self.main_conv(hidden_states)
|
| 142 |
+
main_outputs = self.main_batch_norm(main_outputs)
|
| 143 |
+
|
| 144 |
+
# applies a convolution with a vertical kernel
|
| 145 |
+
if self.vertical_conv is not None:
|
| 146 |
+
vertical_outputs = self.vertical_conv(hidden_states)
|
| 147 |
+
vertical_outputs = self.vertical_batch_norm(vertical_outputs)
|
| 148 |
+
main_outputs = main_outputs + vertical_outputs
|
| 149 |
+
|
| 150 |
+
# applies a convolution with a horizontal kernel
|
| 151 |
+
if self.horizontal_conv is not None:
|
| 152 |
+
horizontal_outputs = self.horizontal_conv(hidden_states)
|
| 153 |
+
horizontal_outputs = self.horizontal_batch_norm(horizontal_outputs)
|
| 154 |
+
main_outputs = main_outputs + horizontal_outputs
|
| 155 |
+
|
| 156 |
+
if self.rbr_identity is not None:
|
| 157 |
+
id_out = self.rbr_identity(hidden_states)
|
| 158 |
+
main_outputs = main_outputs + id_out
|
| 159 |
+
|
| 160 |
+
return self.activation_function(main_outputs)
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
class TextNetStage(nn.Module):
|
| 164 |
+
def __init__(self, config: TextNetConfig, depth: int):
|
| 165 |
+
super().__init__()
|
| 166 |
+
kernel_size = config.conv_layer_kernel_sizes[depth]
|
| 167 |
+
stride = config.conv_layer_strides[depth]
|
| 168 |
+
|
| 169 |
+
num_layers = len(kernel_size)
|
| 170 |
+
stage_in_channel_size = config.hidden_sizes[depth]
|
| 171 |
+
stage_out_channel_size = config.hidden_sizes[depth + 1]
|
| 172 |
+
|
| 173 |
+
in_channels = [stage_in_channel_size] + [stage_out_channel_size] * (num_layers - 1)
|
| 174 |
+
out_channels = [stage_out_channel_size] * num_layers
|
| 175 |
+
|
| 176 |
+
stage = []
|
| 177 |
+
for stage_config in zip(in_channels, out_channels, kernel_size, stride):
|
| 178 |
+
stage.append(TextNetRepConvLayer(config, *stage_config))
|
| 179 |
+
self.stage = nn.ModuleList(stage)
|
| 180 |
+
|
| 181 |
+
def forward(self, hidden_state):
|
| 182 |
+
for block in self.stage:
|
| 183 |
+
hidden_state = block(hidden_state)
|
| 184 |
+
return hidden_state
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
class TextNetEncoder(nn.Module):
|
| 188 |
+
def __init__(self, config: TextNetConfig):
|
| 189 |
+
super().__init__()
|
| 190 |
+
|
| 191 |
+
stages = []
|
| 192 |
+
num_stages = len(config.conv_layer_kernel_sizes)
|
| 193 |
+
for stage_ix in range(num_stages):
|
| 194 |
+
stages.append(TextNetStage(config, stage_ix))
|
| 195 |
+
|
| 196 |
+
self.stages = nn.ModuleList(stages)
|
| 197 |
+
|
| 198 |
+
def forward(
|
| 199 |
+
self,
|
| 200 |
+
hidden_state: torch.Tensor,
|
| 201 |
+
output_hidden_states: bool | None = None,
|
| 202 |
+
return_dict: bool | None = None,
|
| 203 |
+
) -> BaseModelOutputWithNoAttention:
|
| 204 |
+
hidden_states = [hidden_state]
|
| 205 |
+
for stage in self.stages:
|
| 206 |
+
hidden_state = stage(hidden_state)
|
| 207 |
+
hidden_states.append(hidden_state)
|
| 208 |
+
|
| 209 |
+
if not return_dict:
|
| 210 |
+
output = (hidden_state,)
|
| 211 |
+
return output + (hidden_states,) if output_hidden_states else output
|
| 212 |
+
|
| 213 |
+
return BaseModelOutputWithNoAttention(last_hidden_state=hidden_state, hidden_states=hidden_states)
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
@auto_docstring
|
| 217 |
+
class TextNetPreTrainedModel(PreTrainedModel):
|
| 218 |
+
config: TextNetConfig
|
| 219 |
+
base_model_prefix = "textnet"
|
| 220 |
+
main_input_name = "pixel_values"
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
@auto_docstring
|
| 224 |
+
class TextNetModel(TextNetPreTrainedModel):
|
| 225 |
+
def __init__(self, config):
|
| 226 |
+
super().__init__(config)
|
| 227 |
+
self.stem = TextNetConvLayer(config)
|
| 228 |
+
self.encoder = TextNetEncoder(config)
|
| 229 |
+
self.pooler = nn.AdaptiveAvgPool2d((2, 2))
|
| 230 |
+
self.post_init()
|
| 231 |
+
|
| 232 |
+
@auto_docstring
|
| 233 |
+
def forward(
|
| 234 |
+
self,
|
| 235 |
+
pixel_values: Tensor,
|
| 236 |
+
output_hidden_states: bool | None = None,
|
| 237 |
+
return_dict: bool | None = None,
|
| 238 |
+
**kwargs,
|
| 239 |
+
) -> tuple[Any, list[Any]] | tuple[Any] | BaseModelOutputWithPoolingAndNoAttention:
|
| 240 |
+
return_dict = return_dict if return_dict is not None else self.config.return_dict
|
| 241 |
+
output_hidden_states = (
|
| 242 |
+
output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
|
| 243 |
+
)
|
| 244 |
+
|
| 245 |
+
hidden_state = self.stem(pixel_values)
|
| 246 |
+
|
| 247 |
+
encoder_outputs = self.encoder(
|
| 248 |
+
hidden_state, output_hidden_states=output_hidden_states, return_dict=return_dict
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
last_hidden_state = encoder_outputs[0]
|
| 252 |
+
pooled_output = self.pooler(last_hidden_state)
|
| 253 |
+
|
| 254 |
+
if not return_dict:
|
| 255 |
+
output = (last_hidden_state, pooled_output)
|
| 256 |
+
return output + (encoder_outputs[1],) if output_hidden_states else output
|
| 257 |
+
|
| 258 |
+
return BaseModelOutputWithPoolingAndNoAttention(
|
| 259 |
+
last_hidden_state=last_hidden_state,
|
| 260 |
+
pooler_output=pooled_output,
|
| 261 |
+
hidden_states=encoder_outputs[1] if output_hidden_states else None,
|
| 262 |
+
)
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
@auto_docstring(
|
| 266 |
+
custom_intro="""
|
| 267 |
+
TextNet Model with an image classification head on top (a linear layer on top of the pooled features), e.g. for
|
| 268 |
+
ImageNet.
|
| 269 |
+
"""
|
| 270 |
+
)
|
| 271 |
+
class TextNetForImageClassification(TextNetPreTrainedModel):
|
| 272 |
+
def __init__(self, config):
|
| 273 |
+
super().__init__(config)
|
| 274 |
+
self.num_labels = config.num_labels
|
| 275 |
+
self.textnet = TextNetModel(config)
|
| 276 |
+
self.avg_pool = nn.AdaptiveAvgPool2d((1, 1))
|
| 277 |
+
self.flatten = nn.Flatten()
|
| 278 |
+
self.fc = nn.Linear(config.hidden_sizes[-1], config.num_labels) if config.num_labels > 0 else nn.Identity()
|
| 279 |
+
|
| 280 |
+
# classification head
|
| 281 |
+
self.classifier = nn.ModuleList([self.avg_pool, self.flatten])
|
| 282 |
+
|
| 283 |
+
# initialize weights and apply final processing
|
| 284 |
+
self.post_init()
|
| 285 |
+
|
| 286 |
+
@auto_docstring
|
| 287 |
+
def forward(
|
| 288 |
+
self,
|
| 289 |
+
pixel_values: torch.FloatTensor | None = None,
|
| 290 |
+
labels: torch.LongTensor | None = None,
|
| 291 |
+
output_hidden_states: bool | None = None,
|
| 292 |
+
return_dict: bool | None = None,
|
| 293 |
+
**kwargs,
|
| 294 |
+
) -> ImageClassifierOutputWithNoAttention:
|
| 295 |
+
r"""
|
| 296 |
+
labels (`torch.LongTensor` of shape `(batch_size,)`, *optional*):
|
| 297 |
+
Labels for computing the image classification/regression loss. Indices should be in `[0, ...,
|
| 298 |
+
config.num_labels - 1]`. If `config.num_labels == 1` a regression loss is computed (Mean-Square loss), If
|
| 299 |
+
`config.num_labels > 1` a classification loss is computed (Cross-Entropy).
|
| 300 |
+
|
| 301 |
+
Examples:
|
| 302 |
+
```python
|
| 303 |
+
>>> import torch
|
| 304 |
+
>>> import httpx
|
| 305 |
+
>>> from io import BytesIO
|
| 306 |
+
>>> from transformers import TextNetForImageClassification, TextNetImageProcessor
|
| 307 |
+
>>> from PIL import Image
|
| 308 |
+
|
| 309 |
+
>>> url = "http://images.cocodataset.org/val2017/000000039769.jpg"
|
| 310 |
+
>>> with httpx.stream("GET", url) as response:
|
| 311 |
+
... image = Image.open(BytesIO(response.read()))
|
| 312 |
+
|
| 313 |
+
>>> processor = TextNetImageProcessor.from_pretrained("czczup/textnet-base")
|
| 314 |
+
>>> model = TextNetForImageClassification.from_pretrained("czczup/textnet-base")
|
| 315 |
+
|
| 316 |
+
>>> inputs = processor(images=image, return_tensors="pt")
|
| 317 |
+
>>> with torch.no_grad():
|
| 318 |
+
... outputs = model(**inputs)
|
| 319 |
+
>>> outputs.logits.shape
|
| 320 |
+
torch.Size([1, 2])
|
| 321 |
+
```"""
|
| 322 |
+
return_dict = return_dict if return_dict is not None else self.config.return_dict
|
| 323 |
+
|
| 324 |
+
outputs = self.textnet(pixel_values, output_hidden_states=output_hidden_states, return_dict=return_dict)
|
| 325 |
+
last_hidden_state = outputs[0]
|
| 326 |
+
for layer in self.classifier:
|
| 327 |
+
last_hidden_state = layer(last_hidden_state)
|
| 328 |
+
logits = self.fc(last_hidden_state)
|
| 329 |
+
loss = None
|
| 330 |
+
|
| 331 |
+
if labels is not None:
|
| 332 |
+
loss = self.loss_function(labels, logits, self.config)
|
| 333 |
+
|
| 334 |
+
if not return_dict:
|
| 335 |
+
output = (logits,) + outputs[2:]
|
| 336 |
+
return (loss,) + output if loss is not None else output
|
| 337 |
+
|
| 338 |
+
return ImageClassifierOutputWithNoAttention(loss=loss, logits=logits, hidden_states=outputs.hidden_states)
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
@auto_docstring(
|
| 342 |
+
custom_intro="""
|
| 343 |
+
TextNet backbone, to be used with frameworks like DETR and MaskFormer.
|
| 344 |
+
"""
|
| 345 |
+
)
|
| 346 |
+
class TextNetBackbone(BackboneMixin, TextNetPreTrainedModel):
|
| 347 |
+
has_attentions = False
|
| 348 |
+
|
| 349 |
+
def __init__(self, config):
|
| 350 |
+
super().__init__(config)
|
| 351 |
+
|
| 352 |
+
self.textnet = TextNetModel(config)
|
| 353 |
+
self.num_features = config.hidden_sizes
|
| 354 |
+
|
| 355 |
+
# initialize weights and apply final processing
|
| 356 |
+
self.post_init()
|
| 357 |
+
|
| 358 |
+
@can_return_tuple
|
| 359 |
+
@filter_output_hidden_states
|
| 360 |
+
@auto_docstring
|
| 361 |
+
def forward(
|
| 362 |
+
self,
|
| 363 |
+
pixel_values: Tensor,
|
| 364 |
+
output_hidden_states: bool | None = None,
|
| 365 |
+
return_dict: bool | None = None,
|
| 366 |
+
**kwargs,
|
| 367 |
+
) -> tuple[tuple] | BackboneOutput:
|
| 368 |
+
r"""
|
| 369 |
+
Examples:
|
| 370 |
+
|
| 371 |
+
```python
|
| 372 |
+
>>> import torch
|
| 373 |
+
>>> import httpx
|
| 374 |
+
>>> from io import BytesIO
|
| 375 |
+
>>> from PIL import Image
|
| 376 |
+
>>> from transformers import AutoImageProcessor, AutoBackbone
|
| 377 |
+
|
| 378 |
+
>>> url = "http://images.cocodataset.org/val2017/000000039769.jpg"
|
| 379 |
+
>>> with httpx.stream("GET", url) as response:
|
| 380 |
+
... image = Image.open(BytesIO(response.read()))
|
| 381 |
+
|
| 382 |
+
>>> processor = AutoImageProcessor.from_pretrained("czczup/textnet-base")
|
| 383 |
+
>>> model = AutoBackbone.from_pretrained("czczup/textnet-base")
|
| 384 |
+
|
| 385 |
+
>>> inputs = processor(image, return_tensors="pt")
|
| 386 |
+
>>> with torch.no_grad():
|
| 387 |
+
>>> outputs = model(**inputs)
|
| 388 |
+
```"""
|
| 389 |
+
return_dict = return_dict if return_dict is not None else self.config.return_dict
|
| 390 |
+
output_hidden_states = (
|
| 391 |
+
output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
|
| 392 |
+
)
|
| 393 |
+
|
| 394 |
+
outputs = self.textnet(pixel_values, output_hidden_states=True, return_dict=return_dict)
|
| 395 |
+
|
| 396 |
+
hidden_states = outputs.hidden_states if return_dict else outputs[2]
|
| 397 |
+
|
| 398 |
+
feature_maps = ()
|
| 399 |
+
for idx, stage in enumerate(self.stage_names):
|
| 400 |
+
if stage in self.out_features:
|
| 401 |
+
feature_maps += (hidden_states[idx],)
|
| 402 |
+
|
| 403 |
+
if not return_dict:
|
| 404 |
+
output = (feature_maps,)
|
| 405 |
+
if output_hidden_states:
|
| 406 |
+
hidden_states = outputs.hidden_states if return_dict else outputs[2]
|
| 407 |
+
output += (hidden_states,)
|
| 408 |
+
return output
|
| 409 |
+
|
| 410 |
+
return BackboneOutput(
|
| 411 |
+
feature_maps=feature_maps,
|
| 412 |
+
hidden_states=outputs.hidden_states if output_hidden_states else None,
|
| 413 |
+
attentions=None,
|
| 414 |
+
)
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
__all__ = ["TextNetBackbone", "TextNetModel", "TextNetPreTrainedModel", "TextNetForImageClassification"]
|