Icon representing a recipe

Recipe: Bravo Trim Rebuild v1.1

created by bravosk8erboy

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)

Comments