Profile
- Name
- Bravo Trim Rebuild v1.1
- ID
- 109289
- Shared with
- Public
- Parent
- test recipe 02 (trim shake wiggle)
- Children
- Created on
- December 19, 2025 at 02:02 AM UTC
- Updated on
- January 06, 2026 at 22:14 PM UTC
- Description
Best for
Code
-- trim shake and wiggle. handles the segment index changes and sphere selects with trim to speed up work.
-- small trim rebuild with TS&W as fuze for proof of concept
-- when working in trim it is important to use a buffer zone around the trim area to prevent bad geometry at the trim edges.
-- this is why the trim includes a number of segments that never get touched.
-- these rebuild functions get rather confusing with all the trim segment adjustments you need to account for.
-- DNA_RNA_trim_bug_fix() is a quick and dirty way to handle the fact that trim will fail if you don't select the entire DNA or RNA strand.
-- this is written to be easily removed some day. could be faster if fully integrated in single pass, but harder to remove later.
-- recipe runs super fast with client minimized.
-- recipe works well on high wiggle power
-- v1.1
-- added DNA_RNA_trim_bug_fix() to fix trim bug on DNA segments. this is why whole DNA strands are selected.
-- TODO: remove DNA_RNA_check() if this ever gets fixed
-- TODO: remove DNA_RNA_trim_bug_fix() if this ever gets fixed
-- TODO: remove get_DNA_RNA_strands() if this ever gets fixed
-- fixed excessive looping when incrementing i in while loops. speed up segment lookups in:
-- skip_terminal_gap() , check_for_2_terminals() , check_if_terminal()
----------------------------------------------------------------------------------------------------------
-- Computes a symmetric table of pairwise distances between all segments.
-- Returns: distance_table where distance_table[i][j] gives the distance between i and j.
-- Prints progress periodically because this can be expensive for many segments.
function precompute_distances()
print(" Calculating Distance Table: minimize the client and reopen to speed this up")
local total_segments = structure.GetCount()
local distance_table = {}
local total_calculations = (total_segments * (total_segments + 1)) / 2 -- total unique distance calls
local calculations_done = 0 -- counter for progress tracking
local progress_threshold = total_calculations / 10 -- report every 10%
local next_progress = progress_threshold -- next progress threshold to hit
for i = 1, total_segments do -- iterate first segment index
distance_table[i] = { [i] = 0 } -- initialize row and set self-distance
for j = i + 1, total_segments do -- iterate upper-triangle entries
local distance = structure.GetDistance(i, j) -- query structure API for distance
distance_table[i][j] = distance -- store forward entry
-- we skip the below code in favor or just reversing the lookup values at lookup.
-- hopefully this saves memory.
-- TODO: remove below code if it works.
--distance_table[j] = distance_table[j] or {} -- ensure mirrored row exists
--distance_table[j][i] = distance -- store mirrored entry to avoid duplicate calls
end
--[[ debug print
calculations_done = calculations_done + (total_segments - i + 1) -- update completed calculations
if calculations_done >= next_progress then -- if we passed progress threshold
print(string.format("Processed approximately %d%% of segments", (calculations_done / total_calculations) * 100)) -- print progress percent
next_progress = next_progress + progress_threshold -- set next threshold
end
--]]
end
return distance_table -- return populated symmetric distance table
end
-- Finds terminal segments using bands between atom 3 of segment i and atom 1 of segment i+1 (adjacent segments)
-- returns Table or Terminals that is sorted with no duplicates
-- Bravo Find Terminals With Bands v1.1
function findTerminalsWithBands()
save.Quicksave(1)
local segmentCount = structure.GetCount()
-- Clear any existing bands to avoid interference
band.DeleteAll()
-- check and remove false positives from trim.
local success, err = pcall(structure.UntrimPose)
local trim_total_segments = structure.GetCount() -- get total segments after untrim
if trim_total_segments == segmentCount then
--print("Pose was NOT trimmed")
else
print(" Pose is trimmed at start of recipe")
segmentCount = structure.GetCount()
end
-- identify false positives from cuts
local table_of_cuts = structure.GetCuts()
local k = 1
for i = 1, segmentCount - 1 do -- minus 1 so that we don't call a non existant segment with (i+1)
if table_of_cuts[k] == i then
k = k + 1
-- this is a cut, a false positive for terminals. skip it
else
-- Create a band between atom 3 of segment i and atom 1 of segment i+1
band.AddBetweenSegments(i, i + 1, 3, 1)
end
end
local bandCount = band.GetCount()
-- print(" bandCount: " .. bandCount)
local terminals = {}
-- first segment and last segment wont have bands but are always terminals
terminals[#terminals + 1] = 1 -- table is sorted
for i = 1, bandCount do -- minus 1 so that we don't call a non existant segment with (i+1)
-- base before end to keep Terminals table sorted
local Base = band.GetResidueBase(i)
-- print(terminals[#terminals])
-- don't add a duplicate
if terminals[#terminals] ~= Base then
terminals[#terminals + 1] = Base
end
terminals[#terminals + 1] = band.GetResidueEnd(i)
end
-- first segment and last segment wont have bands but are always terminals
-- don't add a duplicate
if terminals[#terminals] ~= segmentCount then
terminals[#terminals + 1] = segmentCount -- table is sorted
end
-- Print all Terminal segment indicies in one line, comma-separated
print(" Terminals")
print(table.concat(terminals, ", "))
-- remove bands and return user created bands
save.Quickload(1)
-- return Terminals is sorted with no duplicates
return terminals
end
-- checks for terminal gaps between two segments.
-- segment_index_1 and segment_index_2 must be 1 number apart.
-- terminals array must be sorted.
function skip_terminal_gap(terminals, segment_index_1, segment_index_2)
if segment_index_1 > segment_index_2 then
segment_index_1, segment_index_2 = segment_index_2, segment_index_1
end
local i = 1
-- terminals[i] here will be false when i is larger then #terminals
-- it operates same as i <= to #terminals
while (terminals[i] and (terminals[i] <= segment_index_1)) do
if terminals[i] == segment_index_1 then
if terminals[i + 1] == segment_index_2 then
return false
end
-- check for terminal gaps or early escape
break
end
i = i + 1
end
return true
end
-- terminals array must be sorted.
function check_if_terminal(terminals, segment_index)
local i = 1
-- terminals[i] here will be false when i is larger then #terminals
-- it operates same as i <= to #terminals
while (terminals[i] and (terminals[i] <= segment_index)) do
if terminals[i] == segment_index then
return true
end
i = i + 1
end
return false
end
-- Evaluates each segment's energy score and returns a list of
-- {index = <segment>, score = <value>} sorted ascending by score.
function evaluate_and_sort_segments()
local total_segments = structure.GetCount()
local segment_scores = {}
-- Evaluate the score of each segment
for i = 1, total_segments do -- loop all segment indices
local score = current.GetSegmentEnergyScore(i) -- request energy score for segment i
segment_scores[#segment_scores + 1] = {index = i, score = score} -- append record with index and score
end
-- Sort the array by score (lowest to highest)
table.sort(segment_scores, function(a, b)
return a.score < b.score
end)
return segment_scores
end
-- Retrieves the precomputed distance between segments i and j from distance_table.
-- Handles either order (i <= j or i > j) as the table stores symmetric entries.
function get_distance(distance_table, i, j)
--debug print
--print("i: ".. i .." j: "..j)
if i <= j then -- prefer the lower-indexed row for lookup
return distance_table[i][j] -- return stored forward value
else
return distance_table[j][i] -- return mirrored value when indices are reversed
end
end
-- In-place Fisher-Yates shuffle of `array`.
function shuffle_array(array)
local n = #array
for i = n, 2, -1 do -- iterate backward through array
local j = math.random(1, i) -- pick random index up to i
array[i], array[j] = array[j], array[i] -- swap elements i and j
end
end
-- finds and returns array of DNA and RNA strand segment indices.
-- returns in format: {{ start_strand_1, end_strand_index_1}, { start_strand_2, end_strand_index_2}}
-- can return any size table including empty table.
function get_DNA_RNA_strands(terminals)
local DNA_RNA_strands = {}
local total_segments = structure.GetCount()
local i = 1
while i <= #terminals do
if DNA_RNA_check(terminals[i]) then
local start_idx = terminals[i]
local end_idx = terminals[i+1]
DNA_RNA_strands[#DNA_RNA_strands + 1] = {start_idx, end_idx}
end
i = i + 2
end
if #DNA_RNA_strands == 0 then
return DNA_RNA_strands
end
local dna_str_list = {}
for _, p in ipairs(DNA_RNA_strands) do
local a = p[1] or ""
local b = p[2] or ""
dna_str_list[#dna_str_list + 1] = tostring(a) .. "-" .. tostring(b)
end
print(" DNA_RNA_strands " .. table.concat(dna_str_list, ", "))
return DNA_RNA_strands
end
-- TODO: remove DNA_RNA_check() function if they ever fix the trim bug on DNA.
function DNA_RNA_check(segment_index)
-- Get the residue/segment code for this index.
local aa = structure.GetAminoAcid(segment_index)
-- Guards: missing/empty codes → treat as "not DNA"
if aa == nil then
return false
end
-- In Foldit, nucleotides use two-letter codes:
-- aa will be length 2 for DNA and RNA.
-- Return true when aa is length 2 (DNA/RNA), false otherwise.
return (#aa == 2)
end
function unique_sorted(unsorted_array)
table.sort(unsorted_array)
local out, last = {}, nil
for _, v in ipairs(unsorted_array) do
if v ~= last then
out[#out+1] = v
last = v
end
end
return out
end
-- array_1 and array_2 are both sorted arrays.
function merge_sorted(array_1, array_2)
local merged = {}
local i, j = 1, 1
while i <= #array_1 and j <= #array_2 do
if array_1[i] < array_2[j] then
merged[#merged + 1] = array_1[i]
i = i + 1
elseif array_1[i] > array_2[j] then
merged[#merged + 1] = array_2[j]
j = j + 1
else
merged[#merged + 1] = array_1[i]
i = i + 1
j = j + 1
end
end
-- Append any remaining elements from either array.
while i <= #array_1 do
merged[#merged + 1] = array_1[i]
i = i + 1
end
while j <= #array_2 do
merged[#merged + 1] = array_2[j]
j = j + 1
end
return merged
end
function DNA_RNA_trim_bug_fix(terminals, buffered_trimmed_set, trimmed_array)
local total_segments = structure.GetCount()
local DNA_RNA_segments = {}
local i, j = 1, 1
-- Walk through trimmed_array and DNA_RNA_strands in order
while i <= #trimmed_array and j <= #DNA_RNA_strands do
local current_seg = trimmed_array[i]
local strand_start = DNA_RNA_strands[j][1]
local strand_end = DNA_RNA_strands[j][2]
-- If current segment is before this strand, advance trimmed pointer
if current_seg < strand_start then
i = i + 1
elseif current_seg > strand_end then
-- current segment is past this strand, advance to next strand
j = j + 1
else
-- current_seg lies within the strand: add entire strand to DNA_RNA_segments
for k = strand_start, strand_end do
DNA_RNA_segments[#DNA_RNA_segments + 1] = k
buffered_trimmed_set[k] = true
-- add adjacent segments where appropriate
if skip_terminal_gap(terminals, k - 1, k) then
buffered_trimmed_set[k - 1] = true
end
if skip_terminal_gap(terminals, k, k + 1) then
buffered_trimmed_set[k + 1] = true
end
end
-- advance trimmed_array index past this strand
while i <= #trimmed_array and trimmed_array[i] <= strand_end do
i = i + 1
end
-- move to next strand
j = j + 1
end
end
if #DNA_RNA_segments > 0 then
trimmed_array = merge_sorted(trimmed_array, DNA_RNA_segments)
end
return buffered_trimmed_set, trimmed_array
end
-- Finds segments around `segment_index`:
-- 1) `trimmed_array`: segments within `sphere_radius` (to trim),
-- 2) `buffered_trimmed_array`: segments within `sphere_radius + trim_buffer` including immediate neighbors,
-- 3) `selection_array`: segments exactly within `sphere_radius` used for later selection mapping.
function find_segments_within_distance(sphere_radius, trim_buffer, distance_table, terminals, start_segment_index, end_segment_index)
-- normalize optional parameters: allow passing a single index as start
if end_segment_index == nil then
end_segment_index = start_segment_index
end
local total_segments = structure.GetCount()
-- this fixes out of range errors. should probably just return when out of range.
start_segment_index, end_segment_index = fix_out_of_range_indices(start_segment_index, end_segment_index)
local trimmed_array = {} -- list of segments inside the trim radius (or buffered radius)
local buffered_trimmed_set = {} -- set keyed by index for buffered coverage
local selection_array = {} -- strict selection list inside sphere_radius
local trim_buffer_radius = sphere_radius + trim_buffer -- buffered radius threshold
-- For each candidate segment, compute the minimum distance to any segment in the start..end range
for i = 1, total_segments do --i segment index is checking all segment index are with in range
local add_to_trim_buffer = false
for k = start_segment_index, end_segment_index do -- k is range index
local dist = get_distance(distance_table, k, i) -- k is range index. i segment index is checking all segment index are with in range
if dist <= trim_buffer_radius then -- still need to check if when inside sphere_radius
add_to_trim_buffer = true
if dist <= sphere_radius then -- short-circuit when inside strict sphere. always inside trime buffer.
selection_array[#selection_array + 1] = i
break
end
end
end
if add_to_trim_buffer then -- if within trim_buffer_radius around the range
trimmed_array[#trimmed_array + 1] = i
buffered_trimmed_set[i] = true
-- skip_terminal_gap fix the problems from terminals and trims buffers
-- if you trim the terminal you wont get the adjacent other terminal in the trim
-- when you trim you get one segment index on both sides of trimmed selection.
-- edge cases to add the connected segment to the buffered array.
-- buffer is the fact that trimmed segments get adjactent segments with segment index. (but do not move)
if skip_terminal_gap(terminals, i - 1, i) then
buffered_trimmed_set[i - 1] = true
end
if skip_terminal_gap(terminals, i, i + 1) then
buffered_trimmed_set[i + 1] = true
end
end
end
-- TODO remove DNA_RNA_trim_bug_fix() function if they ever fix the trim bug on DNA.
if #DNA_RNA_strands > 0 then
buffered_trimmed_set, trimmed_array = DNA_RNA_trim_bug_fix(terminals, buffered_trimmed_set, trimmed_array)
end
-- Convert buffered set to a sorted array of segment indices.
local buffered_trimmed_array = {}
for k = 1, total_segments do
if buffered_trimmed_set[k] then
buffered_trimmed_array[#buffered_trimmed_array + 1] = k
end
end
return trimmed_array, buffered_trimmed_array, selection_array
end
-- Selects contiguous runs of segment indices from a sorted `array`.
-- Uses `selection.Select` for single indices or `selection.SelectRange` for ranges.
function select_continuous_segments(array)
local start_index = 1 -- pointer to start of current contiguous run
while start_index <= #array do -- process runs until we reach the end of the array
local end_index = start_index -- initialize end pointer to start
-- Find the end of the continuous segment by checking consecutive indices
while end_index < #array and array[end_index + 1] == array[end_index] + 1 do
end_index = end_index + 1 -- extend the run while consecutive
end
-- Select the continuous segment as a single index or a range
if start_index == end_index then
selection.Select(array[start_index]) -- single index selection
else
selection.SelectRange(array[start_index], array[end_index]) -- range selection
end
-- Advance to the next potential run after the current end
start_index = end_index + 1
end
end
-- Given `selection_array` of segment indices and `buffered_trimmed_array` (a superset),
-- returns the indices (positions) within `buffered_trimmed_array` that match the selection.
-- both selection_array and buffered_trimmed_array must be sorted arrays.
function select_indices_from_selection_in_buffered(selection_array, buffered_trimmed_array)
local selected_indices = {}
local i, j = 1, 1
local selection_len = #selection_array
local buffered_len = #buffered_trimmed_array
while i <= selection_len and j <= buffered_len do -- walk both arrays to find matching entries
if selection_array[i] == buffered_trimmed_array[j] then
selected_indices[#selected_indices + 1] = j -- store position in buffered array matching the selection
i = i + 1 -- advance selection pointer
j = j + 1 -- advance buffered pointer
elseif selection_array[i] < buffered_trimmed_array[j] then
i = i + 1 -- selection value too small, advance it
else
j = j + 1 -- buffered value too small, advance it
end
end
return selected_indices -- return positions within buffered array corresponding to selection
end
-- Attempts to trim the currently selected segments via `structure.TrimPose`.
-- Returns true if the trim changed the segment count, false otherwise.
function trim()
local total_seg_count = structure.GetCount() -- record count before trim
local success, err = pcall(structure.TrimPose) -- attempt to trim, protected call to avoid crashing
local trim_total_segments = structure.GetCount() -- get count after trim attempt
if trim_total_segments == total_seg_count then -- if nothing was trimmed
print(" No segments trimmed") -- notify user
return false -- indicate no change
end
return true -- indicate trimming changed the segment count
end
-- Attempts to untrim (restore) the pose via `structure.UntrimPose`.
-- Prints an error on failure. Note: the function currently always returns false.
function untrim()
local success, err = pcall(structure.UntrimPose) -- attempt to untrim/restore pose
if success == false then -- if untrim failed
print(" Failed to untrim") -- notify user
return false -- indicate failure
end
return true -- indicate success
end
function check_for_2_terminals(terminals, starting_index, ending_index)
if starting_index > ending_index then
starting_index, ending_index = ending_index, starting_index
end
local count = 0
local num_of_terminals = #terminals
local i = 1
-- terminals[i] here will be false when i is larger then #terminals
-- it operates same as i <= to #terminals
while (terminals[i] and (terminals[i] <= ending_index)) do
local terminal = terminals[i]
if starting_index <= terminal and terminal <= ending_index then
count = count + 1
if count >= 2 then
return true
end
end
i = i + 1
end
return false
end
function check_if_do_range(user_option_skip_terminal_gaps, terminals, starting_index, ending_index)
if user_option_skip_terminal_gaps then
if check_for_2_terminals(terminals, starting_index, ending_index) then
return false
end
end
return true
end
-- Initialize best_score with the current score
save.Quicksave(1)
local best_score = current.GetScore()
local starting_score = best_score
-- Checks the current score against `best_score` and saves if improved.
local recompute_distance_table_score = best_score
function check_and_save_best_score(distance_table, function_name)
local current_score = current.GetScore()
-- Check if the new score is better than the total best score
if current_score > best_score then -- if current score improved
local gain = current_score - best_score -- calculate score gain
best_score = current_score -- update best_score variable
print(string.format(" %.3f | %+.3f | %s", best_score, gain, function_name)) -- notify user
save.Quicksave(1) -- save improved state to quicksave slot
if best_score - recompute_distance_table_score > 20.0 then -- TODO: make variable not hardcoded value
recompute_distance_table_score = best_score
distance_table = precompute_distances()
print(" Significant score improvement detected, recomputing distance table.")
end
else
print(string.format(" %.3f | (%.3f) | %s",
current_score,
best_score,
function_name)) -- otherwise notify no improvement
end
end
function shake_wiggle_trimmed(sphere_radius, trim_buffer, distance_table, terminals, start_segment_index, end_segment_index)
local function_name = "shake_wiggle_trimmed"
local trimmed_array, buffered_trimmed_array, selection_array = find_segments_within_distance(sphere_radius, trim_buffer, distance_table, terminals, start_segment_index, end_segment_index) -- gather local neighborhood
-- Select contiguous trimmed regions and attempt trim
selection.DeselectAll() -- clear current selection
select_continuous_segments(trimmed_array) -- select trimmed indices as continuous runs
local is_it_trimmed = trim() -- attempt trimming and record whether it changed the structure
if is_it_trimmed then -- if trim actually happened, proceed with local optimizations
-- only make selection if trim happened.
local selection_array_indices_from_buffered_trimmed_array = select_indices_from_selection_in_buffered(selection_array, buffered_trimmed_array) -- map selection indices into buffered array positions
select_continuous_segments(selection_array_indices_from_buffered_trimmed_array) -- select mapped buffered positions
structure.ShakeSidechainsSelected(1) -- shake sidechains
structure.WiggleSelected(10) -- repeat wiggle
structure.ShakeSidechainsSelected(1) -- repeat shake
structure.WiggleSelected(10) -- heavy wiggle optimization
is_it_trimmed = untrim() -- attempt restore/untrim after optimization
select_continuous_segments(trimmed_array) -- reselect trimmed array
structure.WiggleSelected(2) -- light final wiggle
check_and_save_best_score(distance_table, function_name)
end
end
function fix_out_of_range_indices(start_segment_index, end_segment_index)
local total_segments = structure.GetCount()
-- this fixes out of range errors. should probably just return when out of range.
if start_segment_index < 1 then
start_segment_index = 1
print(" start_segment_index out of range low")
end
if start_segment_index > total_segments then
start_segment_index = total_segments
print(" start_segment_index out of range total_segments")
end
if end_segment_index < 1 then
end_segment_index = 1
print(" end_segment_index out of range low")
end
if end_segment_index > total_segments then
end_segment_index = total_segments
print(" end_segment_index out of range total_segments")
end
-- ensure start <= end
if start_segment_index > end_segment_index then
start_segment_index, end_segment_index = end_segment_index, start_segment_index
end
return start_segment_index, end_segment_index
end
-- expand range by 1 on each side, excluding terminals
function expand_range_by_1_exclude_terminals(terminals, start_segment_index, end_segment_index)
local total_segments = structure.GetCount()
-- if not a terminal segment it will expand by 1
if not check_if_terminal(terminals, start_segment_index) then
start_segment_index = start_segment_index - 1
end
if not check_if_terminal(terminals, end_segment_index) then
end_segment_index = end_segment_index + 1
end
return start_segment_index, end_segment_index
end
-- TODO: update description
-- Finds segments around `segment_index`:
-- 1) `trimmed_array`: segments within `sphere_radius` (to trim),
-- 2) `buffered_trimmed_array`: segments within `sphere_radius + trim_buffer` including immediate neighbors,
-- 3) `selection_array`: segments exactly within `sphere_radius` used for later selection mapping.
function find_segments_for_trimmed_rebuild(terminals, start_segment_index, end_segment_index)
-- normalize optional parameters: allow passing a single index as start
if end_segment_index == nil then
end_segment_index = start_segment_index
end
local total_segments = structure.GetCount()
-- this fixes out of range errors. should probably just return when out of range.
start_segment_index, end_segment_index = fix_out_of_range_indices(start_segment_index, end_segment_index)
local trimmed_array = {} -- list of segments inside the trim radius (or buffered radius)
local buffered_trimmed_set = {} -- set keyed by index for buffered coverage
local selection_array = {} -- strict selection list
-- For each candidate segment, compute the minimum distance to any segment in the start..end range
for i = start_segment_index, end_segment_index do --i segment index is checking all segment index are with in range
selection_array[#selection_array + 1] = i
end
-- expand selection by one. this is so the rebuild has a buffer zone.
-- this doesnt expand around terminals since you can just rebuild the terminal.
local adjust_start_segment_index, adjust_end_segment_index = expand_range_by_1_exclude_terminals(terminals, start_segment_index, end_segment_index)
-- create buffer table for segment index correction.
for i = adjust_start_segment_index, adjust_end_segment_index do --i segment index is checking all segment index are with in range
trimmed_array[#trimmed_array + 1] = i
buffered_trimmed_set[i] = true
-- skip_terminal_gap fix the problems from terminals and trims buffers
-- if you trim the terminal you wont get the adjacent other terminal in the trim
-- when you trim you get one segment index on both sides of trimmed selection.
-- edge cases to add the connected segment to the buffered array.
-- buffer is the fact that trimmed segments get adjactent segments with segment index. (but do not move)
if skip_terminal_gap(terminals, i - 1, i) then
buffered_trimmed_set[i - 1] = true
end
if skip_terminal_gap(terminals, i, i + 1) then
buffered_trimmed_set[i + 1] = true
end
end
-- TODO remove DNA_RNA_trim_bug_fix() function if they ever fix the trim bug on DNA.
if DNA_RNA_check(adjust_start_segment_index) or DNA_RNA_check(adjust_end_segment_index) then
buffered_trimmed_set, trimmed_array = DNA_RNA_trim_bug_fix(terminals, buffered_trimmed_set, trimmed_array)
end
-- Convert buffered set to a sorted array of segment indices.
local buffered_trimmed_array = {}
for k = 1, total_segments do
if buffered_trimmed_set[k] then
buffered_trimmed_array[#buffered_trimmed_array + 1] = k
end
end
return trimmed_array, buffered_trimmed_array, selection_array
end
function rebuild_trimmmed( distance_table, terminals, start_segment_index, end_segment_index)
local function_name = "rebuild_trimmmed"
local trimmed_array, buffered_trimmed_array, selection_array = find_segments_for_trimmed_rebuild(terminals, start_segment_index, end_segment_index) -- gather local neighborhood
-- Select contiguous trimmed regions and attempt trim
selection.DeselectAll() -- clear current selection
select_continuous_segments(trimmed_array) -- select trimmed indices as continuous runs
local is_it_trimmed = trim() -- attempt trimming and record whether it changed the structure
if is_it_trimmed then -- if trim actually happened, proceed with local optimizations
local selection_array_indices_from_buffered_trimmed_array = select_indices_from_selection_in_buffered(selection_array, buffered_trimmed_array) -- map selection indices into buffered array positions
select_continuous_segments(selection_array_indices_from_buffered_trimmed_array) -- select mapped buffered positions
--[[ debug print
print(" selection_array")
print(table.concat(selection_array, ", "))
print(" trimmed_array")
print(table.concat(trimmed_array, ", "))
print(" buffered_trimmed_array")
print(table.concat(buffered_trimmed_array, ", "))
print(" selection_array_indices_from_buffered_trimmed_array")
print(table.concat(selection_array_indices_from_buffered_trimmed_array, ", "))
--]]
structure.RebuildSelected(1)
is_it_trimmed = untrim() -- attempt restore/untrim after optimization
select_continuous_segments(trimmed_array) -- reselect trimmed array
structure.ShakeSidechainsSelected(2) -- light final shake
--structure.WiggleSelected(4) -- light final wiggle
check_and_save_best_score(distance_table, function_name)
end
end
-- settings -- GUI
function GUI_get_settings()
local rc
repeat
local dlog = dialog.CreateDialog ("=== Trim Rebuild v1.0 ===")
-- TODO finish implementing GUI options
dlog.label_rxx = dialog.AddLabel("RXX = remix or rebuild")
dlog.run_remix = dialog.AddCheckbox ( "Use remix (else rebuild)" , run_remix )
dlog.f_idealize = dialog.AddCheckbox ("Idealize instead of fuse" , fidealize)
dlog.rb_worst = dialog.AddCheckbox ( "RXX worst segments (else RXX all)" , rebuild_worst )
dlog.rebuild_all = dialog.AddCheckbox ( "RXX all (after RXX worst)" , rebuild_all )
dlog.rebuild_over_terminal_gaps = dialog.AddCheckbox ( "Rebuild over Terminal Gaps" , rebuild_over_terminal_gaps )
dlog.rb_worst_how_many = dialog.AddSlider ( "How many worst" , how_many , 3 , structure.GetCount() , 0 )
dlog.starting_rb_length = dialog.AddSlider ( "RXX start length" , 3 , 1 , 13 , 0 )
dlog.minLength = dialog.AddSlider("From length:",3,1,13,0)
dlog.maxLength = dialog.AddSlider("To length:",9,1,13,0)
dlog.rlabel_list = dialog.AddLabel("RXX Segments:")
dlog.rebuild_list = dialog.AddTextbox ( "RXX segs" , rebuild_str )
dlog.rlabela = dialog.AddLabel('List segs to RXX like 1-3,6,19-30,45-62')
dlog.rebuild_criterion = dialog.AddSlider ( "RXX criterion" , ss_criterion , 1 , 4 , 0 )
dlog.rc = dialog.AddLabel ( "1 = L only, 2 = L only + 1, 3 = L + E, 4 = Any" )
dlog.n_rebuilds = dialog.AddSlider ( "# of RXX" , number_of_rebuilds , 1 , 40 , 0 )
dlog.CIfactor = dialog.AddSlider ( "CI factor", CIfactor, 0.1, 1, 2 )
dlog.label_minGain = dialog.AddLabel("Min Gain to repeat length for more points")
dlog.min_gain_to_repeat_loop = dialog.AddSlider ("Min Gain", min_gain_to_repeat_loop, 0.001, (5*structure.GetCount()) , 2 )
dlog.label_length_loop_minGain = dialog.AddLabel("Min Gain to repeat all lengths for more points")
dlog.all_length_min_gain_to_repeat_loop = dialog.AddSlider ("AllLengthsMinGain", all_length_min_gain_to_repeat_loop, 0.001, (5*structure.GetCount()) , 2 )
dlog.div_1 = dialog.AddLabel ( "__________________________________________" )
if DoesPuzzleHaveSlowFilters () then
score_type = ST_SLOWFILT
end
dlog.score_type = dialog.AddSlider ( "Score type" , score_type , 1 , 5 , 0 )
dlog.tp = dialog.AddLabel ( "1 = Normal, 2 = Hiding, 3 = Bonding" )
dlog.tp_2 = dialog.AddLabel ( "4 = Filters, 5 = Normal for Slow Filters" )
dlog.DEBUGRUN = dialog.AddCheckbox ( "Debug", DEBUGRUN )
dlog.ok = dialog.AddButton ( "OK" , 1 )
dlog.cancel = dialog.AddButton ( "Cancel" , 0 )
dlog.other_options = dialog.AddButton ( "Other options" , 2 )
rc = dialog.Show ( dlog )
if rc > 0 then
run_remix = dlog.run_remix.value
fidealize = dlog.f_idealize.value
rebuild_worst = dlog.rb_worst.value
rebuild_all = dlog.rebuild_all.value
rebuild_over_terminal_gaps = dlog.rebuild_over_terminal_gaps.value
how_many = dlog.rb_worst_how_many.value
rebuild_length = dlog.starting_rb_length.value
minLength = dlog.minLength.value
maxLength = dlog.maxLength.value
rebuild_list = getlist(dlog.rebuild_list.value)
rebuild_str = printlist(rebuild_list)
ss_criterion = dlog.rebuild_criterion.value
number_of_rebuilds = dlog.n_rebuilds.value
CIfactor = dlog.CIfactor.value
min_gain_to_repeat_loop = dlog.min_gain_to_repeat_loop.value
all_length_min_gain_to_repeat_loop = dlog.all_length_min_gain_to_repeat_loop.value
score_type = dlog.score_type.value
DEBUGRUN = dlog.DEBUGRUN.value
if rc == 2 then
GetOptionalParameters ()
end
end
until rc <= 1
return rc > 0
end
-- Main loop: prepares distance table, orders segments by score, and for each segment:
-- selects nearby segments, trims them, applies wiggle/shake operations, untrims,
-- then checks and saves improved scores.
function main()
print("=== Trim Rebuild v1.0 ===")
print(" recipe runs much faster when minimized.")
print(string.format(" Starting score: %.3f", best_score))
local terminals = findTerminalsWithBands()
local total_segments = structure.GetCount()
-- TODO: remove DNA_RNA_strands if they ever fix the trim bug on DNA.
DNA_RNA_strands = get_DNA_RNA_strands(terminals)
-- Shuffle the segment indices to randomize the order
-- shuffle_array(segment_indices)
-- Sort worst score to highest score.
local segment_indices = {}
segment_indices = evaluate_and_sort_segments()
-- TODO: make user option
local user_option_skip_terminal_gaps = true -- Example user option
local sphere_radius = 12 -- Example sphere radius
local trim_buffer = 14 -- Example trim buffer
-- Precompute distances
local distance_table = precompute_distances() -- build symmetric distance table used by neighbor queries
for k = 1, 5 do -- adjustable parameter for range size around each segment
local length = (2 * k + 1)
print(string.format(" Starting Length: %d", length))
-- iteration loop
for idx = 1, #segment_indices do -- iterate through ordered segment records (numeric loop)
-- at this point segment_indices is sorted from worst to best score.
-- segment will be a list of {index = <segment>, score = <value>}
local segment = segment_indices[idx]
local start_segment_index = segment.index - k -- Extract the index field from record TODO: fix this random selection
local end_segment_index = segment.index + k -- TODO: fix this random selection
if check_if_do_range(user_option_skip_terminal_gaps, terminals, start_segment_index, end_segment_index) then
print(string.format(" %d/%d | working %d - %d", idx, total_segments, start_segment_index, end_segment_index))
save.Quickload(1) -- restore best pose before attempting changes
shake_wiggle_trimmed(sphere_radius, trim_buffer, distance_table, terminals, start_segment_index, end_segment_index)
rebuild_trimmmed( distance_table, terminals, start_segment_index, end_segment_index)
shake_wiggle_trimmed(sphere_radius, trim_buffer, distance_table, terminals, start_segment_index, end_segment_index)
else
print(string.format(" %d/%d | Skipping %d - %d", idx, total_segments, start_segment_index, end_segment_index))
end
end
save.Quickload(1) -- restore best pose before attempting changes
-- Re-evaluate and re-sort segments after each full pass so you start with the worst again.
-- next pass should be a different length, if not probably should keep same order..
segment_indices = evaluate_and_sort_segments()
end
ending_print()
end
function ending_print()
save.Quickload(1)
local total_gain = best_score - starting_score
print("=== Trim Rebuild v1.0 ===")
print(string.format(" End. Total gain: %.3f | Score: %.3f", total_gain, best_score))
end
-- Error handler used with `xpcall` to print errors and restore the last saved best state.
function handle_error(err)
print()
print(" Error occurred: " .. err)
save.Quickload(1)
ending_print()
return
end
-- Call main function using xpcall to handle errors
xpcall(main, handle_error)