From c6c93663f691d3af3b80b2128b158bb724edd9b4 Mon Sep 17 00:00:00 2001 From: Dave Doty Date: Mon, 22 May 2023 12:29:14 -0700 Subject: [PATCH 1/9] Update __version__.py --- nuad/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nuad/__version__.py b/nuad/__version__.py index 1bf14c59..55a4d029 100644 --- a/nuad/__version__.py +++ b/nuad/__version__.py @@ -1 +1 @@ -version = '0.4.2' # version line; WARNING: do not remove or change this line or comment +version = '0.4.3' # version line; WARNING: do not remove or change this line or comment From a801382ab6993909a25172d7ed8e9e1abb29885d Mon Sep 17 00:00:00 2001 From: David Doty Date: Sat, 10 Jun 2023 15:45:40 -0700 Subject: [PATCH 2/9] Update search.py --- nuad/search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nuad/search.py b/nuad/search.py index 4ecbfa77..0e6636c6 100644 --- a/nuad/search.py +++ b/nuad/search.py @@ -1924,7 +1924,7 @@ def _assert_violations_are_accurate(evaluations: Dict[Constraint, Dict[nc.Part, @dataclass class Evaluation(Generic[DesignPart]): - # Represents a violation of a single :any:`Constraint` in a :any:`Design`. + # Represents an evaluation of a single :any:`Constraint` in a :any:`Design`. # The "part" of the :any:`Design` that was evaluated for the constraint is generic type `DesignPart` # (e.g., for :any:`StrandPairConstraint`, DesignPart = :any:`Pair` [:any:`Strand`]). From 969a0d99cf9c087dc691d444897da39e79ef0875 Mon Sep 17 00:00:00 2001 From: David Doty Date: Mon, 12 Jun 2023 17:34:58 -0700 Subject: [PATCH 3/9] Update constraints.py --- nuad/constraints.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nuad/constraints.py b/nuad/constraints.py index b5259b07..20be9a16 100644 --- a/nuad/constraints.py +++ b/nuad/constraints.py @@ -146,7 +146,6 @@ class M13Variant(enum.Enum): (https://www.tilibit.com/pages/contact-us) """ - def length(self) -> int: """ :return: length of this variant of M13 (e.g., 7249 for variant :data:`M13Variant.p7249`) From f339f44b10f1637fb8449c5e111ed11b29c2e981 Mon Sep 17 00:00:00 2001 From: David Doty Date: Tue, 18 Jul 2023 21:24:15 -0700 Subject: [PATCH 4/9] Update constraints.py --- nuad/constraints.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nuad/constraints.py b/nuad/constraints.py index 20be9a16..fc3bd8ec 100644 --- a/nuad/constraints.py +++ b/nuad/constraints.py @@ -2335,7 +2335,7 @@ class Strand(Part, JSONSerializable, Generic[StrandLabel, DomainLabel]): def __init__(self, domains: Iterable[Domain[DomainLabel]] | None = None, - starred_domain_indices: Iterable[int] | None = None, + starred_domain_indices: Iterable[int] = (), group: str = default_strand_group, name: str | None = None, label: StrandLabel | None = None, @@ -2373,7 +2373,6 @@ def __init__(self, # d._check_subdomain_graph_is_uniquely_assignable() # noqa self.domains = list(domains) # type: ignore - starred_domain_indices = [] if starred_domain_indices is None else starred_domain_indices self.starred_domain_indices = frozenset(starred_domain_indices) # type: ignore self.label = label self.idt = idt @@ -4304,7 +4303,6 @@ class Result(Generic[DesignPart]): _summary: Optional[str] = None - value: pint.Quantity[Decimal] | None = None """ If this is a "numeric" constraint, i.e., checking some number such as the complex free energy of a From 7e465da103d9dae397ba34bccdf21406b6b55b98 Mon Sep 17 00:00:00 2001 From: David Doty Date: Wed, 19 Jul 2023 16:10:09 -0700 Subject: [PATCH 5/9] made formatting of units in text reports shorter --- notebooks/Untitled.ipynb | 24 +++++++++++++++++------- nuad/constraints.py | 8 ++++++-- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/notebooks/Untitled.ipynb b/notebooks/Untitled.ipynb index 0f6b8fe2..eadc7d37 100644 --- a/notebooks/Untitled.ipynb +++ b/notebooks/Untitled.ipynb @@ -2,23 +2,33 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 20, "id": "cf3567b8-b41b-4ce0-aa83-f8cbd4dd45b3", "metadata": {}, "outputs": [ { "data": { + "image/png": "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\n", "text/plain": [ - "(0.796078431372549, 0.3764705882352941, 0.08235294117647059)" + "
" ] }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" } ], "source": [ - "(203/255, 96/255, 21/255)" + "import nuad.np as nn\n", + "import matplotlib.pyplot as plt\n", + "\n", + "s = nn.DNASeqList(length=21, num_random_seqs=10**5)\n", + "energies = s.energies(37)\n", + "# print(f'{min(energies)=}')\n", + "# print(f'{max(energies)=}')\n", + "plt.figure(figsize=(18,8))\n", + "_ = plt.hist(energies, bins=20)" ] }, { @@ -82,7 +92,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.8.16" } }, "nbformat": 4, diff --git a/nuad/constraints.py b/nuad/constraints.py index fc3bd8ec..e28bca3c 100644 --- a/nuad/constraints.py +++ b/nuad/constraints.py @@ -1320,7 +1320,6 @@ def _generate_random_sequences_passing_numpy_filters(self, rng: np.random.Genera num_to_generate: int) -> nn.DNASeqList: bases = self._bases_to_use() length = self.length - _length_threshold_numpy = math.floor(math.log(num_to_generate, 4)) seqs = nn.DNASeqList(length=length, alphabet=bases, shuffle=True, num_random_seqs=num_to_generate, rng=rng) seqs_passing_numpy_filters = self._apply_numpy_filters(seqs) @@ -4352,7 +4351,12 @@ def summary(self) -> str: It can be set explicitly, or calculated from :data:`Result.value` if not set explicitly. """ if self._summary is None: - return str(self.value) + # This formatting is "short pretty": https://pint.readthedocs.io/en/stable/user/formatting.html + # e.g., kcal/mol instead of kilocalorie / mol + # also 2 decimal places to make numbers line up nicely + self.value.default_format = '.2fP~' + summary_str = f'{self.value}' + return str(summary_str) else: return self._summary From 16a9af8a97275ba8ca41dea669607ef77123be85 Mon Sep 17 00:00:00 2001 From: David Doty Date: Thu, 20 Jul 2023 09:02:24 -0700 Subject: [PATCH 6/9] changed default unit format from pretty (Unicode) to compact (ASCII) --- nuad/constraints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nuad/constraints.py b/nuad/constraints.py index e28bca3c..7037fab7 100644 --- a/nuad/constraints.py +++ b/nuad/constraints.py @@ -4354,7 +4354,7 @@ def summary(self) -> str: # This formatting is "short pretty": https://pint.readthedocs.io/en/stable/user/formatting.html # e.g., kcal/mol instead of kilocalorie / mol # also 2 decimal places to make numbers line up nicely - self.value.default_format = '.2fP~' + self.value.default_format = '.2fC~' summary_str = f'{self.value}' return str(summary_str) else: From f1c34948980c8d164f7238d87ea4ef4b401fcc8d Mon Sep 17 00:00:00 2001 From: David Doty Date: Sun, 23 Jul 2023 08:43:06 -0700 Subject: [PATCH 7/9] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 32395f6e..5d4907d7 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ In more detail, there are five main types of objects you create to describe your ## Constraint evaluations must be pure functions of their inputs -For all constraints, it is critical that the `evaluate` or `evaluate_bulk` functions be *pure* functions of their inputs: the return value should depend only on the parameters passed to the function. For example, a `StrandPairConstraint` takes two strands as input, and its `(excess, summary)` return values should depend *only* on those two strands. Similarly, a `StrandsConstraint`, whose `evaluate_bulk` function takes a list of strands as input, should return a list of tuples, where each tuple represents a violation of a strand that depends only on that strand. This is required because nuad does an optimization in which constraints are only evaluated if they depend on parts of the design that contain the domain(s) that changed in the current iteration. +For all constraints, it is critical that the `evaluate` or `evaluate_bulk` functions be *pure* functions of their inputs: the return value should depend only on the parameters passed to the function. For example, a `StrandPairConstraint` takes two strands as input, and its `Result` return values should depend *only* on those two strands. Similarly, a `StrandsConstraint`, whose `evaluate_bulk` function takes a list of strands as input, should return a list of tuples, where each tuple represents a violation of a strand that depends only on that strand. This is required because nuad does an optimization in which constraints are only evaluated if they depend on parts of the design that contain the domain(s) that changed in the current iteration. For example, suppose there are 100 strands, but only 3 strands contain the domain `x`, and `x` is the domain whose DNA sequence is changed in the current search iteration. Then each `StrandConstraint` `s` will be evaluated only on those 3 strands, on the assumption that the other 97 strands would have the same output of the function `s.evaluate` as before. From 011867e7197d5c2dbca6a573ae5fc2fb190516e5 Mon Sep 17 00:00:00 2001 From: David Doty Date: Sun, 23 Jul 2023 10:09:15 -0700 Subject: [PATCH 8/9] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5d4907d7..f79cdf53 100644 --- a/README.md +++ b/README.md @@ -138,9 +138,9 @@ In more detail, there are five main types of objects you create to describe your - `Constraint`: There are several kinds of constraint objects. Not all of them are related in the type hierarchy. - **"hard" constraints on Domain sequences:** - These are the strictest constraints, which do not even allow certain `Domain` sequences to be considered. They are applied by a `DomainPool` before allowing a sequence to be returned from `DomainPool.generate_sequence()`. These are of two types: `NumpyConstraint` and `SequenceConstraint`. Each of them indicates whether a DNA sequence is allowed or not; for instance a constraint forbidding 4 G's in a row would permit AGGGTT but forbid AGGGGT. The difference between them is that a `NumpyConstraint` operates on many DNA sequences at a time, representing them as a 2D numpy byte array (e.g., a 1000 × 15 array of bytes to represent 1000 sequences, each of length 15), and for operations that numpy is suited for, can evaluate these constraints *much* faster than the equivalent Python code that would loop over each sequence individually. However, if you have a constraint that is not straightforward to express using numpy operations, then a `SequenceConstraint` can be used to express it in plain Python. A `SequenceConstraint` is simply a type alias for a Python function that takes a string as input representing the DNA sequence and returns a Boolean indicating whether the sequence satisfies the constraint. Due to the speed of numpy, it is advised to use `SequenceConstraint`'s only if necessary because it cannot be expressed as a `NumpyConstraint`. + These are the strictest constraints, which do not even allow certain `Domain` sequences to be considered, known as "filters". They are applied by a `DomainPool` before allowing a sequence to be returned from `DomainPool.generate_sequence()`, which is the method called whenever the search algorithm wants to try a new DNA sequence for a `Domain`. These are of two types of filters: `NumpyFilter` and `SequenceFilter`. Each of them indicates whether a DNA sequence is allowed or not; for instance a filter forbidding 4 G's in a row would permit AGGGTT but forbid AGGGGT. The difference between them is that a `NumpyFilter` operates on many DNA sequences at a time, representing them as a 2D numpy byte array (e.g., a 1000 × 15 array of bytes to represent 1000 sequences, each of length 15), and for operations that numpy is suited for, can evaluate these filters *much* faster than the equivalent Python code that would loop over each sequence individually. However, if you have a filter that is not straightforward to express using numpy operations, then a `SequenceFilter` can be used to express it in plain Python. A `SequenceFilter` is simply a type alias for a Python function that takes a string as input representing the DNA sequence and returns a Boolean indicating whether the sequence satisfies the filter. Due to the speed of numpy, it is advised to use `SequenceFilter`'s only if necessary because it cannot be expressed as a `NumpyFilter`. - - **"soft" constraints:** All other constraints are subclasses of the abstract superclass `Constraint`. These constrains are "softer": sequences violating the constraints are allowed to be assigned to `Domain`'s. The sequence design algorithm steadily improves the design by changing sequences until all of these constraints are satisfied. The different subtypes of the base class `Constraint` correspond to different parts of the `Design` that are being evaluated by the `Constraint`. The types are: + - **"soft" constraints:** All other constraints are subclasses of the abstract superclass `Constraint`. These constrains are "softer" than filters as described above: sequences violating the constraints are allowed to be assigned to `Domain`'s. The sequence design algorithm steadily improves the design by changing sequences until all of these constraints are satisfied. The different subtypes of the base class `Constraint` correspond to different parts of the `Design` that are being evaluated by the `Constraint`. The types are: - `SingularConstraint`: This is an abstract superclass of the following concrete subclasses. The difference with the other abstract superclass `BulkConstraint` is explained in `BulkConstraint` below. From ed35f236a528e33cbff5a39d5849b6c3372c4fab Mon Sep 17 00:00:00 2001 From: David Doty Date: Sun, 23 Jul 2023 10:21:38 -0700 Subject: [PATCH 9/9] changed type of `Strand.label` and `Domain.label` from arbitrary object to `str` and removed generic declarations with `StrandLabel` and `DomainLabel` --- nuad/constraints.py | 166 ++++++++++++++++---------------------------- 1 file changed, 61 insertions(+), 105 deletions(-) diff --git a/nuad/constraints.py b/nuad/constraints.py index 7037fab7..b673a8ba 100644 --- a/nuad/constraints.py +++ b/nuad/constraints.py @@ -1411,11 +1411,8 @@ def individual_parts(self) -> Tuple[Domain, ...] | Tuple[Strand, ...]: pass -DomainLabel = TypeVar('DomainLabel') - - @dataclass -class Domain(Part, JSONSerializable, Generic[DomainLabel]): +class Domain(Part, JSONSerializable): """ Represents a contiguous substring of the DNA sequence of a :any:`Strand`, which is intended to be either single-stranded, or to bind fully to the Watson-Crick complement of the :any:`Domain`. @@ -1476,15 +1473,12 @@ class Domain(Part, JSONSerializable, Generic[DomainLabel]): Note: If a domain is fixed then all of its subdomains must also be fixed. """ - label: DomainLabel | None = None + label: str | None = None """ - Optional generic "label" object to associate to this :any:`Domain`. + Optional "label" string to associate to this :any:`Domain`. Useful for associating extra information with the :any:`Domain` that will be serialized, for example, - for DNA sequence design. It must be an object (e.g., a dict or primitive type such as str or int) - that is naturally JSON serializable. (Calling - `json.dumps `_ - on the object should succeed without having to specify a custom encoder.) + for DNA sequence design. """ dependent: bool = False @@ -1522,7 +1516,7 @@ class Domain(Part, JSONSerializable, Generic[DomainLabel]): """ def __init__(self, name: str, pool: DomainPool | None = None, sequence: str | None = None, - fixed: bool = False, label: DomainLabel | None = None, dependent: bool = False, + fixed: bool = False, label: str | None = None, dependent: bool = False, subdomains: List[Domain] | None = None, weight: float | None = None) -> None: if subdomains is None: subdomains = [] @@ -1600,9 +1594,8 @@ def to_json_serializable(self, suppress_indent: bool = True) -> NoIndent | Dict[ @staticmethod def from_json_serializable(json_map: Dict[str, Any], - pool_with_name: Dict[str, DomainPool] | None, - label_decoder: Callable[[Any], DomainLabel] = lambda label: label) \ - -> Domain[DomainLabel]: + pool_with_name: Dict[str, DomainPool] | None) \ + -> Domain: """ :param json_map: JSON serializable object encoding this :any:`Domain`, as returned by @@ -1611,9 +1604,6 @@ def from_json_serializable(json_map: Dict[str, Any], dict mapping name to :any:`DomainPool` with that name; required to rehydrate :any:`Domain`'s. If None, then a DomainPool with no constraints is created with the name and domain length found in the JSON. - :param label_decoder: - Function transforming object deserialized from JSON (e.g, dict, list, string) into an object - of type DomainLabel. :return: :any:`Domain` represented by dict `json_map`, assuming it was created by :py:meth:`Domain.to_json_serializable`. @@ -1622,8 +1612,7 @@ def from_json_serializable(json_map: Dict[str, Any], sequence: str | None = json_map.get(sequence_key) fixed: bool = json_map.get(fixed_key, False) - label_json: Any = json_map.get(label_key) - label = label_decoder(label_json) + label: str = json_map.get(label_key) pool: DomainPool | None pool_name: str | None = json_map.get(domain_pool_name_key) @@ -1635,8 +1624,7 @@ def from_json_serializable(json_map: Dict[str, Any], else: pool = None - domain: Domain[DomainLabel] = Domain( - name=name, sequence=sequence, fixed=fixed, pool=pool, label=label) + domain: Domain = Domain(name=name, sequence=sequence, fixed=fixed, pool=pool, label=label) return domain @property @@ -2257,14 +2245,12 @@ def _check_idt_string_not_none_or_empty(value: str, field_name: str) -> None: default_strand_group = 'default_strand_group' -StrandLabel = TypeVar('StrandLabel') - @dataclass -class Strand(Part, JSONSerializable, Generic[StrandLabel, DomainLabel]): +class Strand(Part, JSONSerializable): """Represents a DNA strand, made of several :any:`Domain`'s. """ - domains: List[Domain[DomainLabel]] + domains: List[Domain] """The :any:`Domain`'s on this :any:`Strand`, in order from 5' end to 3' end.""" starred_domain_indices: FrozenSet[int] @@ -2321,23 +2307,20 @@ class Strand(Part, JSONSerializable, Generic[StrandLabel, DomainLabel]): and for an internal modification that goes between bases, the allowed indices are 0,...,n-2. """ - label: StrandLabel | None = None + label: str | None = None """ - Optional generic "label" object to associate to this :any:`Strand`. + Optional generic "label" string to associate to this :any:`Strand`. Useful for associating extra information with the :any:`Strand` that will be serialized, for example, - for DNA sequence design. It must be an object (e.g., a dict or primitive type such as str or int) - that is naturally JSON serializable. (Calling - `json.dumps `_ - on the object should succeed without having to specify a custom encoder.) + for DNA sequence design. """ def __init__(self, - domains: Iterable[Domain[DomainLabel]] | None = None, + domains: Iterable[Domain] | None = None, starred_domain_indices: Iterable[int] = (), group: str = default_strand_group, name: str | None = None, - label: StrandLabel | None = None, + label: str | None = None, idt: IDTFields | None = None, ) -> None: """ @@ -2570,9 +2553,8 @@ def to_json_serializable(self, suppress_indent: bool = True) -> NoIndent | Dict[ @staticmethod def from_json_serializable(json_map: Dict[str, Any], - domain_with_name: Dict[str, Domain[DomainLabel]], - label_decoder: Callable[[Any], StrandLabel] = (lambda label: label), - ) -> Strand[StrandLabel, DomainLabel]: + domain_with_name: Dict[str, Domain], + ) -> Strand: """ :return: :any:`Strand` represented by dict `json_map`, assuming it was created by @@ -2580,20 +2562,19 @@ def from_json_serializable(json_map: Dict[str, Any], """ name: str = mandatory_field(Strand, json_map, name_key) domain_names_json = mandatory_field(Strand, json_map, domain_names_key) - domains: List[Domain[DomainLabel]] = [domain_with_name[name] for name in domain_names_json] + domains: List[Domain] = [domain_with_name[name] for name in domain_names_json] starred_domain_indices = mandatory_field(Strand, json_map, starred_domain_indices_key) group = json_map.get(group_key, default_strand_group) - label_json = json_map.get(label_key) - label = label_decoder(label_json) + label: str = json_map.get(label_key) idt_json = json_map.get(idt_key) idt = None if idt_json is not None: idt = IDTFields.from_json_serializable(idt_json) - strand: Strand[StrandLabel, DomainLabel] = Strand( + strand: Strand = Strand( domains=domains, starred_domain_indices=starred_domain_indices, group=group, name=name, label=label, idt=idt) return strand @@ -2601,27 +2582,27 @@ def from_json_serializable(json_map: Dict[str, Any], def __repr__(self) -> str: return self.name - def unstarred_domains(self) -> List[Domain[DomainLabel]]: + def unstarred_domains(self) -> List[Domain]: """ :return: list of unstarred :any:`Domain`'s in this :any:`Strand`, in order they appear in :py:data:`Strand.domains` """ return [domain for idx, domain in enumerate(self.domains) if idx not in self.starred_domain_indices] - def starred_domains(self) -> List[Domain[DomainLabel]]: + def starred_domains(self) -> List[Domain]: """ :return: list of starred :any:`Domain`'s in this :any:`Strand`, in order they appear in :py:data:`Strand.domains` """ return [domain for idx, domain in enumerate(self.domains) if idx in self.starred_domain_indices] - def unstarred_domains_set(self) -> OrderedSet[Domain[DomainLabel]]: + def unstarred_domains_set(self) -> OrderedSet[Domain]: """ :return: set of unstarred :any:`Domain`'s in this :any:`Strand` """ return OrderedSet(self.unstarred_domains()) - def starred_domains_set(self) -> OrderedSet[Domain[DomainLabel]]: + def starred_domains_set(self) -> OrderedSet[Domain]: """ :return: set of starred :any:`Domain`'s in this :any:`Strand` """ @@ -2666,7 +2647,7 @@ def fixed(self) -> bool: """True if every :any:`Domain` on this :any:`Strand` has a fixed DNA sequence.""" return all(domain.fixed for domain in self.domains) - def unfixed_domains(self) -> Tuple[Domain[DomainLabel]]: + def unfixed_domains(self) -> Tuple[Domain]: """ :return: all :any:`Domain`'s in this :any:`Strand` where :py:data:`Domain.fixed` is False """ @@ -2821,7 +2802,7 @@ def set_fixed_sequence(self, seq: str) -> None: @dataclass -class DomainPair(Part, Generic[DomainLabel], Iterable[Domain]): +class DomainPair(Part, Iterable[Domain]): domain1: Domain domain2: Domain @@ -2852,13 +2833,13 @@ def individual_parts(self) -> Tuple[Domain, ...]: def fixed(self) -> bool: return self.domain1.fixed and self.domain2.fixed - def __iter__(self) -> Iterator[Strand]: + def __iter__(self) -> Iterator[Domain]: yield self.domain1 yield self.domain2 @dataclass -class StrandPair(Part, Generic[StrandLabel, DomainLabel], Iterable[Strand]): +class StrandPair(Part, Iterable[Strand]): strand1: Strand strand2: Strand @@ -2895,7 +2876,7 @@ def __iter__(self) -> Iterator[Strand]: @dataclass -class Complex(Part, Generic[StrandLabel, DomainLabel], Iterable[Strand]): +class Complex(Part, Iterable[Strand]): strands: Tuple[Strand, ...] """The strands in this complex.""" @@ -3055,7 +3036,7 @@ def min_wells_per_plate(self) -> int: @dataclass -class Design(Generic[StrandLabel, DomainLabel], JSONSerializable): +class Design(JSONSerializable): """ Represents a complete design, i.e., a set of DNA :any:`Strand`'s with domains, and :any:`Constraint`'s on the sequences @@ -3067,7 +3048,7 @@ class Design(Generic[StrandLabel, DomainLabel], JSONSerializable): # for example caching in a Constraint all pairs of domains in the Design, in case the Constraint # is reused for multiple designs in the same program. - strands: List[Strand[StrandLabel, DomainLabel]] + strands: List[Strand] """List of all :any:`Strand`'s in this :any:`Design`.""" _domains_interned: Dict[str, Domain] @@ -3075,14 +3056,14 @@ class Design(Generic[StrandLabel, DomainLabel], JSONSerializable): ################################################# # derived fields, so not specified in constructor - domains: List[Domain[DomainLabel]] = field(init=False) + domains: List[Domain] = field(init=False) """ List of all :any:`Domain`'s in this :any:`Design`. (without repetitions) Computed from :py:data:`Design.strands`, so not specified in constructor. """ - strands_by_group_name: Dict[str, List[Strand[StrandLabel, DomainLabel]]] = field(init=False) + strands_by_group_name: Dict[str, List[Strand]] = field(init=False) """ Dict mapping each group name to a list of the :any:`Strand`'s in this :any:`Design` in the group. @@ -3213,59 +3194,37 @@ def write_design_file(self, directory: str = '.', filename: str | None = None, sc.write_file_same_name_as_running_python_script(content, extension, directory, filename) @staticmethod - def from_design_file(filename: str, - strand_label_decoder: Callable[[Any], StrandLabel] = lambda label: label, - domain_label_decoder: Callable[[Any], DomainLabel] = lambda label: label, - ) -> Design[StrandLabel, DomainLabel]: + def from_design_file(filename: str) -> Design: """ :param filename: name of JSON file describing the :any:`Design` - :param domain_label_decoder: - Function that transforms JSON representation of :py:data:`Domain.label` into the proper type. - :param strand_label_decoder: - Function that transforms JSON representation of :py:data:`Strand.label` into the proper type. :return: :any:`Design` described by the JSON file with name `filename`, assuming it was created using :py:meth`Design.to_json`. """ with open(filename, 'r') as f: json_str = f.read() - return Design.from_json(json_str, strand_label_decoder, domain_label_decoder) + return Design.from_json(json_str) @staticmethod - def from_json(json_str: str, - strand_label_decoder: Callable[[Any], StrandLabel] = lambda label: label, - domain_label_decoder: Callable[[Any], DomainLabel] = lambda label: label, - ) -> Design[StrandLabel, DomainLabel]: + def from_json(json_str: str) -> Design: """ :param json_str: The string representing the :any:`Design` as a JSON object. - :param domain_label_decoder: - Function that transforms JSON representation of :py:data:`Domain.label` into the proper type. - :param strand_label_decoder: - Function that transforms JSON representation of :py:data:`Strand.label` into the proper type. :return: :any:`Design` described by this JSON string, assuming it was created using :py:meth`Design.to_json`. """ json_map = json.loads(json_str) - design: Design[StrandLabel, DomainLabel] = Design.from_json_serializable( - json_map, domain_label_decoder=domain_label_decoder, strand_label_decoder=strand_label_decoder) + design: Design = Design.from_json_serializable(json_map) return design @staticmethod - def from_json_serializable(json_map: Dict[str, Any], - domain_label_decoder: Callable[[Any], DomainLabel] = lambda label: label, - strand_label_decoder: Callable[[Any], StrandLabel] = lambda label: label, - ) -> 'Design[StrandLabel, DomainLabel]': + def from_json_serializable(json_map: Dict[str, Any]) -> Design: """ :param json_map: JSON serializable object encoding this :any:`Design`, as returned by :py:meth:`Design.to_json_serializable`. - :param domain_label_decoder: - Function that transforms JSON representation of :py:data:`Domain.label` into the proper type. - :param strand_label_decoder: - Function that transforms JSON representation of :py:data:`Strand.label` into the proper type. :return: :any:`Design` represented by dict `json_map`, assuming it was created by :py:meth:`Design.to_json_serializable`. No constraints are populated. @@ -3276,16 +3235,13 @@ def from_json_serializable(json_map: Dict[str, Any], domains_json = mandatory_field(Design, json_map, domains_key) domains: List[Domain] = [ - Domain.from_json_serializable(domain_json, pool_with_name=pool_with_name, - label_decoder=domain_label_decoder) + Domain.from_json_serializable(domain_json, pool_with_name=pool_with_name) for domain_json in domains_json] domain_with_name = {domain.name: domain for domain in domains} strands_json = mandatory_field(Design, json_map, strands_key) - strands = [Strand.from_json_serializable( - json_map=strand_json, domain_with_name=domain_with_name, - label_decoder=strand_label_decoder) - for strand_json in strands_json] + strands = [Strand.from_json_serializable(json_map=strand_json, domain_with_name=domain_with_name) + for strand_json in strands_json] # modifications in whole design if nm.design_modifications_key in json_map: @@ -3301,11 +3257,11 @@ def from_json_serializable(json_map: Dict[str, Any], def add_strand(self, domain_names: List[str] | None = None, - domains: List[Domain[DomainLabel]] | None = None, + domains: List[Domain] | None = None, starred_domain_indices: Iterable[int] | None = None, group: str = default_strand_group, name: str | None = None, - label: StrandLabel | None = None, + label: str | None = None, idt: IDTFields | None = None, ) -> Strand: """ @@ -3631,7 +3587,7 @@ def domain_pools(self) -> List[DomainPool]: """ return list(self.domain_pools_to_domain_map.keys()) - def domains_by_pool_name(self, domain_pool_name: str) -> List[Domain[DomainLabel]]: + def domains_by_pool_name(self, domain_pool_name: str) -> List[Domain]: """ :param domain_pool_name: name of a :any:`DomainPool` :return: the :any:`Domain`'s in `domain_pool` @@ -3647,7 +3603,7 @@ def from_scadnano_file( sc_filename: str, fix_assigned_sequences: bool = True, ignored_strands: Iterable[Strand] | None = None - ) -> Design[StrandLabel, DomainLabel]: + ) -> Design: """ Converts a scadnano Design stored in file named `sc_filename` to a a :any:`Design` for doing DNA sequence design. @@ -3675,10 +3631,10 @@ def from_scadnano_file( return Design.from_scadnano_design(sc_design, fix_assigned_sequences, ignored_strands) @staticmethod - def from_scadnano_design(sc_design: sc.Design[StrandLabel, DomainLabel], + def from_scadnano_design(sc_design: sc.Design, fix_assigned_sequences: bool = True, ignored_strands: Iterable[Strand] | None = None, - warn_existing_domain_labels: bool = True) -> Design[StrandLabel, DomainLabel]: + warn_existing_domain_labels: bool = True) -> Design: """ Converts a scadnano Design `sc_design` to a a :any:`Design` for doing DNA sequence design. Each Strand name and Domain name from the scadnano Design are assigned as @@ -3755,7 +3711,7 @@ def from_scadnano_design(sc_design: sc.Design[StrandLabel, DomainLabel], # make dsd StrandGroups, taking names from Strands and Domains, # and assign (and maybe fix) DNA sequences strand_names: Set[str] = set() - design: Design[StrandLabel, DomainLabel] = Design() + design: Design = Design() for group, sc_strands in sc_strand_groups.items(): for sc_strand in sc_strands: # do not include strands with the same name more than once @@ -3767,10 +3723,10 @@ def from_scadnano_design(sc_design: sc.Design[StrandLabel, DomainLabel], domain_names: List[str] = [domain.name for domain in sc_strand.domains] sequence = sc_strand.dna_sequence - nuad_strand: Strand[StrandLabel, DomainLabel] = design.add_strand(domain_names=domain_names, - group=group, - name=sc_strand.name, - label=sc_strand.label) + nuad_strand: Strand = design.add_strand(domain_names=domain_names, + group=group, + name=sc_strand.name, + label=sc_strand.label) # assign sequence if sequence is not None: for dsd_domain, sc_domain in zip(nuad_strand.domains, sc_strand.domains): @@ -3808,7 +3764,7 @@ def get_group_name_from_strand_label(sc_strand: Strand) -> Any: else: raise AssertionError(f'label does not have either an attribute or a dict key "{group_key}"') - def assign_fields_to_scadnano_design(self, sc_design: sc.Design[StrandLabel, DomainLabel], + def assign_fields_to_scadnano_design(self, sc_design: sc.Design, ignored_strands: Iterable[Strand] = (), overwrite: bool = False): """ @@ -3821,7 +3777,7 @@ def assign_fields_to_scadnano_design(self, sc_design: sc.Design[StrandLabel, Dom self.assign_idt_fields_to_scadnano_design(sc_design, ignored_strands, overwrite) self.assign_modifications_to_scadnano_design(sc_design, ignored_strands, overwrite) - def assign_sequences_to_scadnano_design(self, sc_design: sc.Design[StrandLabel, DomainLabel], + def assign_sequences_to_scadnano_design(self, sc_design: sc.Design, ignored_strands: Iterable[Strand] = (), overwrite: bool = False) -> None: """ @@ -3926,7 +3882,7 @@ def assign_strand_groups_to_labels(self, sc_design: sc.Design, f'Set overwrite to True to force an overwrite.') sc_strand.label[group_key] = nuad_strand.group - def assign_idt_fields_to_scadnano_design(self, sc_design: sc.Design[StrandLabel, DomainLabel], + def assign_idt_fields_to_scadnano_design(self, sc_design: sc.Design, ignored_strands: Iterable[Strand] = (), overwrite: bool = False) -> None: """ @@ -3957,7 +3913,7 @@ def assign_idt_fields_to_scadnano_design(self, sc_design: sc.Design[StrandLabel, f'Set overwrite to True to force an overwrite.') sc_strand.idt = nuad_strand.idt.to_scadnano_idt() - def assign_modifications_to_scadnano_design(self, sc_design: sc.Design[StrandLabel, DomainLabel], + def assign_modifications_to_scadnano_design(self, sc_design: sc.Design, ignored_strands: Iterable[Strand] = (), overwrite: bool = False) -> None: """ @@ -4011,8 +3967,8 @@ def assign_modifications_to_scadnano_design(self, sc_design: sc.Design[StrandLab def _assign_to_strand_without_checking_existing_sequence( self, - sc_strand: sc.Strand[StrandLabel, DomainLabel], - sc_design: sc.Design[StrandLabel, DomainLabel] + sc_strand: sc.Strand, + sc_design: sc.Design ) -> None: # check types if not isinstance(sc_design, sc.Design): @@ -4038,8 +3994,8 @@ def _assign_to_strand_without_checking_existing_sequence( sc_strand.set_dna_sequence(strand_sequence) @staticmethod - def _assign_to_strand_with_partial_sequence(sc_strand: sc.Strand[StrandLabel, DomainLabel], - sc_design: sc.Design[StrandLabel, DomainLabel], + def _assign_to_strand_with_partial_sequence(sc_strand: sc.Strand, + sc_design: sc.Design, sc_domain_name_tuples: Dict[Tuple[str, ...], Strand]) -> None: # check types