A Construct is an ordered collection of Segments representing a complete biological design. Just as a gene is assembled from regulatory and coding parts (a promoter, ribosome binding site, coding sequence, and terminator), a Construct assembles Segment objects into a coherent whole.The Construct handles validation (all segments must share the same type), auto-labeling, and, crucially, joined sequence concatenation, which gives the full designed sequence after optimization.
Constructs enforce several rules at creation time to catch design errors early:
All segments must share the same sequence type. DNA and protein segments cannot be mixed in one Construct. To represent a gene and its protein product, use separate Constructs.
All segments must share the same valid character set. If one segment uses custom valid_chars, all segments in the Construct must use the same set.
Segment labels must be unique within a Construct. Duplicate labels cause a ValueError.
The most important property of a Construct is joined_sequences. After optimization, this gives the full concatenated sequence from all segments, with merged metadata from each segment.
python
# After optimization, get the full sequencesfor seq in construct.joined_sequences: print(f"Full sequence ({len(seq)} bp): {seq.sequence[:50]}...") print(f"Segments: {list(seq.metadata.get('segments', {}).keys())}")
When the optimizer selects multiple results per segment (e.g., top-3), joined_sequences pairs them by index:
python
# If each segment has 3 result sequences:# segment_1.result_sequences = [Seq("AAA"), Seq("TTT"), Seq("GGG")]# segment_2.result_sequences = [Seq("CCC"), Seq("AAA"), Seq("TTT")]construct.joined_sequences# [Sequence("AAACCC"), # index 0 from each segment# Sequence("TTTAAA"), # index 1 from each segment# Sequence("GGGTTT")] # index 2 from each segment
All segments in a Construct must have the same number of result sequences. joined_sequences raises a RuntimeError if the per-segment result pools have mismatched lengths.
Optimizers take a list of Constructs to optimize. Multiple optimizers in a Program must share the same Construct objects by identity so that results persist between stages.
python
# Correct: same construct object passed to both stagesconstruct = Construct([promoter, cds], label="my_design")stage1 = RejectionSamplingOptimizer( constructs=[construct], # same object generators=[broad_gen], constraints=[fast_constraint], config=RejectionSamplingOptimizerConfig(num_samples=5000, num_results=50))stage2 = MCMCOptimizer( constructs=[construct], # same object: results flow through generators=[fine_gen], constraints=[expensive_constraint], config=MCMCOptimizerConfig(num_steps=500))program = Program(optimizers=[stage1, stage2], num_results=10)program.run()# Results are in the construct's segmentsfor seq in construct.joined_sequences: print(seq.sequence)
Do not create separate Construct instances for each optimizer stage. The result sequences from stage 1 would be lost. Always reuse the same Construct object.