Skip to content

SDC — Strand Displacement Circuits

rgrow.sdc

SDCParams dataclass

Holds all input parameters required to define an SDC system.

Attributes:

Name Type Description
k_f float

Forward reaction rate constant.

temperature float

Initial simulation temperature in Celsius.

glue_dg_s Mapping[str | tuple[str, str], tuple[float, float] | str]

Dictionary of glue strengths (ΔG, ΔS), indexed by name or tuple of names.

scaffold list[str | None]

Ordered list of glue names representing the scaffold.

strands list[SDCStrand]

List of all strands in solution.

scaffold_concentration float

Effective molar concentration of the scaffold.

k_n float
k_c float
junction_penalty_dg float | None

Optional ΔG penalty for forming junctions (in kcal/mol).

junction_penalty_ds float | None

Optional ΔS penalty for forming junctions (in kcal/mol/K).

Source code in rgrow-python/rgrow/sdc/sdc.py
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
@dataclasses.dataclass
class SDCParams:
    """
    Holds all input parameters required to define an SDC system.

    Attributes
    ----------
    k_f : float
        Forward reaction rate constant.
    temperature : float
        Initial simulation temperature in Celsius.
    glue_dg_s : Mapping[str | tuple[str, str], tuple[float, float] | str]
        Dictionary of glue strengths (ΔG, ΔS), indexed by name or tuple of names.
    scaffold : list[str | None]
        Ordered list of glue names representing the scaffold.
    strands : list[SDCStrand]
        List of all strands in solution.
    scaffold_concentration : float
        Effective molar concentration of the scaffold.
    k_n : float
    k_c : float
    junction_penalty_dg : float | None
        Optional ΔG penalty for forming junctions (in kcal/mol).
    junction_penalty_ds : float | None
        Optional ΔS penalty for forming junctions (in kcal/mol/K).
    """

    k_f: float = 1.0e6
    temperature: float = 80.0
    glue_dg_s: (
        Mapping[str | tuple[str, str], tuple[float, float] | str]
        | Mapping[str, tuple[float, float] | str]
        | Mapping[tuple[str, str], tuple[float, float] | str]
    ) = field(default_factory=dict)
    scaffold: list[str | None] = field(
        default_factory=list
    )  # | list[list[str | None]] # FIXME: can't deal with typing for this
    strands: list[SDCStrand] = field(default_factory=list)
    quencher_concentration: float = 0.0
    quencher_name: str | None = None
    fluorophore_concentration: float = 0.0
    reporter_name: str | None = None
    scaffold_concentration: float = 1e-100
    k_n: float = 0.0
    k_c: float = 0.0
    junction_penalty_dg: float | None = None
    junction_penalty_ds: float | None = None

    def __str__(self) -> str:
        strands_info = ""
        for strand in self.strands:
            strands_info += "\n\t" + strand.__str__()
        return f"Forward Rate: {self.k_f}\nStrands: {strands_info}\nScaffold: " + ", ".join(x if x is not None else 'None' for x in self.scaffold[2:-2])

    def to_dict(self) -> dict:
        return dataclasses.asdict(self)

    def write_json(self, filename: str) -> None:
        with open(filename, "w+") as f:
            json.dump(self.to_dict(), f)

    def write_yaml(self, filename: str) -> None:
        with open(filename, "w+") as f:
            yaml.dump(self, f)

    @classmethod
    def from_dict(cls, d: dict) -> "SDCParams":
        if "strands" in d:
            d["strands"] = [SDCStrand(**strand) for strand in d["strands"]]
        if "scaffold" in d:
            if (
                d["scaffold"][0] is None
                and d["scaffold"][1] is None
                and d["scaffold"][-1] is None
                and d["scaffold"][-2] is None
            ):
                d["scaffold"] = d["scaffold"][2:-2]
        return cls(**d)

    @classmethod
    def read_json(cls, filename: str) -> "SDCParams":
        with open(filename, "r") as f:
            d = json.load(f)
        return cls.from_dict(d)

    @classmethod
    def read_yaml(cls, filename: str) -> "SDCParams":
        with open(filename, "r") as f:
            d: SDCParams = yaml.load(f, Loader=yaml.Loader)
        return d

SDC

Bases: SDC

The actual SDC model

Attributes:

Name Type Description
params SDCParams

Parameters object used for initialization.

name str

User-provided name of the system.

Source code in rgrow-python/rgrow/sdc/sdc.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
class SDC(rg.rgrow.SDC):
    """
    The actual SDC model

    Attributes
    ----------
    params : SDCParams
        Parameters object used for initialization.
    name : str
        User-provided name of the system.
    """

    params: SDCParams
    name: str | None = None

    def __new__(cls, params: SDCParams, name: str | None = None) -> "SDC":
        self = super().__new__(cls, params)
        self.params = params
        self.name = name
        return self

    @property
    def rgrow_system(self):
        return self

    def __str__(self):
        header_line = f"SDC System {self.name} info:"
        strand_info = f"Parameters:\n{self.params.__str__()}"
        return f"{header_line}\n{strand_info}\n\n"

    def run_anneal(self, anneal: Anneal):
        """"
        Given some Anneal, this will run the system on that anneal.

        This will run the system on a standard square state, with no tracking.

        Parameters
        ----------
        anneal : Anneal

        Returns
        -------
        AnnealOutputs
        """
        times, temperatures = anneal.gen_arrays()
        scaffold_len = len(self.params.scaffold)

        # Here we will keep the state of the canvas at each point in time
        canvas_arr = np.zeros(
            (len(temperatures), anneal.scaffold_count, scaffold_len), dtype=int
        )

        # Now we make a state, and let the time pass ...
        state = rg.State(
            (anneal.scaffold_count, scaffold_len),
            "square",
            "none",
            # The strands defined by the user + null + fluorophore + quencher
            len(self.params.strands) + 3,
        )

        pbar = tqdm.tqdm(enumerate(temperatures), total=len(temperatures))
        for i, t in pbar:
            pbar.set_description(f"{t:7.3f}°C")

            self.set_param("temperature", t)
            self.update_all(state)
            self.evolve(state, for_time=anneal.timestep)
            canvas_arr[i, :, :] = state.canvas_view

        return AnnealOutputs(self, canvas_arr, anneal, state)  # type: ignore[arg-type]

run_anneal(anneal)

" Given some Anneal, this will run the system on that anneal.

This will run the system on a standard square state, with no tracking.

Parameters:

Name Type Description Default
anneal Anneal
required

Returns:

Type Description
AnnealOutputs
Source code in rgrow-python/rgrow/sdc/sdc.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
def run_anneal(self, anneal: Anneal):
    """"
    Given some Anneal, this will run the system on that anneal.

    This will run the system on a standard square state, with no tracking.

    Parameters
    ----------
    anneal : Anneal

    Returns
    -------
    AnnealOutputs
    """
    times, temperatures = anneal.gen_arrays()
    scaffold_len = len(self.params.scaffold)

    # Here we will keep the state of the canvas at each point in time
    canvas_arr = np.zeros(
        (len(temperatures), anneal.scaffold_count, scaffold_len), dtype=int
    )

    # Now we make a state, and let the time pass ...
    state = rg.State(
        (anneal.scaffold_count, scaffold_len),
        "square",
        "none",
        # The strands defined by the user + null + fluorophore + quencher
        len(self.params.strands) + 3,
    )

    pbar = tqdm.tqdm(enumerate(temperatures), total=len(temperatures))
    for i, t in pbar:
        pbar.set_description(f"{t:7.3f}°C")

        self.set_param("temperature", t)
        self.update_all(state)
        self.evolve(state, for_time=anneal.timestep)
        canvas_arr[i, :, :] = state.canvas_view

    return AnnealOutputs(self, canvas_arr, anneal, state)  # type: ignore[arg-type]

SDCStrand dataclass

Represents a strand that can bind to the scaffold in the SDC model.

Parameters:

Name Type Description Default
concentration float

Concentration of the strand (default is 1 μM = 1e-6 M).

1e-06
left_glue str or None

Glue name (or None) for the left attachment point.

None
btm_glue str or None

Identifier for the central domain of the strand (typically a base like 'A', 'B', etc.).

None
right_glue str or None

Glue name (or None) for the right attachment point.

None
name str or None

Human-readable identifier (e.g., compact code like "0A1").

None
color str or None

Optional color for visualization or grouping.

None
Source code in rgrow-python/rgrow/sdc/strand.py
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
@dataclass
class SDCStrand:
    """
        Represents a strand that can bind to the scaffold in the SDC model.

        Parameters
        ----------
        concentration : float
            Concentration of the strand (default is 1 μM = 1e-6 M).
        left_glue : str or None
            Glue name (or `None`) for the left attachment point.
        btm_glue : str or None
            Identifier for the central domain of the strand (typically a base like 'A', 'B', etc.).
        right_glue : str or None
            Glue name (or `None`) for the right attachment point.
        name : str or None
            Human-readable identifier (e.g., compact code like "0A1").
        color : str or None
            Optional color for visualization or grouping.
    """

    concentration: float = 1e-6
    left_glue: str | None = None
    btm_glue: str | None = None
    right_glue: str | None = None
    name: str | None = None
    color: str | None = None

    @classmethod
    def basic_from_string(string_representation: str):
        """
        Given some simple string, generate a strand.

        For example:
        - given "0A0", the strand with left glue 0*, right glue 0, and base A will be generated
        - given "-B-", the strand with no glue on the left and right, and base B will be generated
        """

        return SDCStrand(
            left_glue=f"{string_representation[0]}*",
            btm_glue=string_representation[1],
            right_glue=string_representation[2],
            name=string_representation,
        )

    @classmethod
    def pair_glue_from_string(string_representation: str):
        """
        Given some string, generate a strand.

        This function will only work for simple systems (that is, small systems, with not-so high complexity), the input
        MUST be in the following format: f"{left_glue}{base_character}{right_glue}", where the left and right glue are
        either a number, or '-' if no glue is present, and the base_character is A, or B, ..., or Z.

        Some valid strings would be "0A1", "1B1", "0K4", "-A1", "-E-"
        """

        # An even base will have an even right glue, and an odd left glue. An odd base will have an odd right glue and
        # an even left glue
        even_base = (ord(string_representation[1]) - ord("A")) % 2 == 0

        l_postfix = "e" if even_base else "o"
        r_postfix = "o" if even_base else "e"

        lc = string_representation[0]
        rc = string_representation[2]
        l_glue = None if lc == "-" else f"{lc}{l_postfix}"
        r_glue = None if rc == "-" else f"{rc}{r_postfix}"

        return SDCStrand(
            left_glue=l_glue,
            btm_glue=string_representation[1],
            right_glue=r_glue,
            name=string_representation,
        )

    def __str__(self):
        return f"Strand {self.name} at concentration {self.concentration}"

basic_from_string(string_representation) classmethod

Given some simple string, generate a strand.

For example: - given "0A0", the strand with left glue 0*, right glue 0, and base A will be generated - given "-B-", the strand with no glue on the left and right, and base B will be generated

Source code in rgrow-python/rgrow/sdc/strand.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@classmethod
def basic_from_string(string_representation: str):
    """
    Given some simple string, generate a strand.

    For example:
    - given "0A0", the strand with left glue 0*, right glue 0, and base A will be generated
    - given "-B-", the strand with no glue on the left and right, and base B will be generated
    """

    return SDCStrand(
        left_glue=f"{string_representation[0]}*",
        btm_glue=string_representation[1],
        right_glue=string_representation[2],
        name=string_representation,
    )

pair_glue_from_string(string_representation) classmethod

Given some string, generate a strand.

This function will only work for simple systems (that is, small systems, with not-so high complexity), the input MUST be in the following format: f"{left_glue}{base_character}{right_glue}", where the left and right glue are either a number, or '-' if no glue is present, and the base_character is A, or B, ..., or Z.

Some valid strings would be "0A1", "1B1", "0K4", "-A1", "-E-"

Source code in rgrow-python/rgrow/sdc/strand.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
@classmethod
def pair_glue_from_string(string_representation: str):
    """
    Given some string, generate a strand.

    This function will only work for simple systems (that is, small systems, with not-so high complexity), the input
    MUST be in the following format: f"{left_glue}{base_character}{right_glue}", where the left and right glue are
    either a number, or '-' if no glue is present, and the base_character is A, or B, ..., or Z.

    Some valid strings would be "0A1", "1B1", "0K4", "-A1", "-E-"
    """

    # An even base will have an even right glue, and an odd left glue. An odd base will have an odd right glue and
    # an even left glue
    even_base = (ord(string_representation[1]) - ord("A")) % 2 == 0

    l_postfix = "e" if even_base else "o"
    r_postfix = "o" if even_base else "e"

    lc = string_representation[0]
    rc = string_representation[2]
    l_glue = None if lc == "-" else f"{lc}{l_postfix}"
    r_glue = None if rc == "-" else f"{rc}{r_postfix}"

    return SDCStrand(
        left_glue=l_glue,
        btm_glue=string_representation[1],
        right_glue=r_glue,
        name=string_representation,
    )