Icon representing a recipe

Recipe: Contactulator 1.0

created by LociOiling

Profile


Name
Contactulator 1.0
ID
102066
Shared with
Public
Parent
None
Children
None
Created on
May 28, 2016 at 06:50 AM UTC
Updated on
May 28, 2016 at 06:50 AM UTC
Description

Calculates the weighted and unweighted contact bonuses, and compares them to the current total bonus. The user may adjust the default of 50 points per contact if a mismatch occurs.

Best for


Code


--[[ Contaculator Original Author: LociOiling Version 1.0 28 May 2016 This recipe calculates the contact bonus. A bonus of 50 points per made contact is used as the starting point. Depending on the puzzle, bonus may be weighted or unweighted. In a "weighted" puzzle, the bonus is multiplied by the "weight" or "heat" of a given contact. So if the bonus is 50 points, but the contact only has a weight of 0.5, the made contact receives only 25 points. (Somehow, it seems like a contact assigned less weight should generate a higher bonus when made, but maybe that's just me.) In an "unweighted" puzzle, each made contact simply receives 50 points or the bonus value for the puzzle in question. The recipe calculates the total weighted and unweighted contact bonus, and compares it the current total bonus. It announces the scoring method if one of calculated scores matches the total bonus. If there's no match, the user can change the base bonus and click the "recalc" button. Most of the code for this recipe was lifted from "Contact Cement". The recipe output contains some no doubt fascinating information about the distribution of contacts. ]]-- -- -- globals section -- Recipe = "Contaculator" Version = "1.0" ReVersion = Recipe .. " v" .. Version tHeat = 0 minHeat = 0.5 maxHeat = 0.9 meanHeat = 0.7 mediHeat = 0.7 madeTotal = 0 madePct = 0 unmadeTotal = 0 unmadePct = 0 minMadeDist = 99999 maxMadeDist = 0 minUnmadeDist = 99999 maxUnmadeDist = 0 tMadeDist = 0 tUnmadeDist = 0 variableHeat = false unweightedBonus = 0 weightedBonus = 0 contactBonus = 50 -- -- heat map table indexes -- MAPSEG1 = 1 -- heat [ 1 ] = seg1 MAPSEG2 = 2 -- heat [ 2 ] = seg2 MAPHEAT = 3 -- heat [ 3 ] = heat MAPSS1 = 4 -- heat [ 4 ] = ss seg1 MAPSS2 = 5 -- heat [ 5 ] = ss seg2 MAPDIST = 6 -- heat [ 6 ] = dist MAPCONT = 7 -- heat [ 7 ] = in contact MAPUBON = 8 -- heat [ 8 ] = unweighted bonus MAPWBON = 9 -- heat [ 9 ] = weighted bonus segCnt = structure.GetCount() segCnt2 = segCnt while structure.GetSecondaryStructure ( segCnt2 ) == "M" do segCnt2 = segCnt2 - 1 end segStart = 1 segEnd = segCnt2 -- -- end of globals section -- function r3 ( i ) return i - i % 0.001 end function SortByIndex ( tab, indx ) local ii local jj for ii = 1, #tab - 1 do for jj = ii + 1, #tab do local h1 = tab [ ii ] [ indx ] local h2 = tab [ jj ] [ indx ] if h2 > h1 then tab [ ii ], tab [ jj ] = tab [ jj ], tab [ ii ] end end end return tab end function GetOnlyContacts() tHeat = 0 local heatmap = {} for ii = segStart, segEnd - 1 do -- 04/06/2014 for jj = ii + 1, segEnd do -- 04/06/2014 if ( ii ~= jj ) then local heat = contactmap.GetHeat ( ii, jj ) if ( heat > 0 ) then local con = contactmap.IsContact ( ii, jj ) local ubon = 0 local wbon = 0 heatmap [ #heatmap + 1 ] = heatpnt if con then madeTotal = madeTotal + 1 ubon = contactBonus wbon = contactBonus * heat else unmadeTotal = unmadeTotal + 1 end local ss1 = structure.GetSecondaryStructure ( ii ) local ss2 = structure.GetSecondaryStructure ( jj ) local dist = structure.GetDistance ( ii, jj ) local heatpnt = { ii, jj, heat, ss1, ss2, dist, con, ubon, wbon } heatmap [ #heatmap + 1 ] = heatpnt tHeat = tHeat + heat unweightedBonus = unweightedBonus + ubon weightedBonus = weightedBonus + wbon end end end end return heatmap end function GetContacts( ) local ii local jj local heatmap = GetOnlyContacts() print ( #heatmap .. " contacts with non-zero heat found" ) madePct = r3 ( ( madeTotal / #heatmap ) * 100 ) print ( madeTotal .. " made contacts (" .. madePct .. "%)" ) if ( #heatmap > 0 ) then SortByIndex ( heatmap, MAPHEAT ) minHeat = 9999999 maxHeat = 0 for ii = 1, #heatmap do if heatmap [ ii ] [ MAPHEAT ] > maxHeat then maxHeat = heatmap [ ii ] [ MAPHEAT ] end if heatmap [ ii ] [ MAPHEAT ] < minHeat then minHeat = heatmap [ ii ] [ MAPHEAT ] end if heatmap [ ii ] [ MAPCONT ] then tMadeDist = tMadeDist + heatmap [ ii ] [ MAPDIST ] if heatmap [ ii ] [ MAPDIST ] > maxMadeDist then maxMadeDist = heatmap [ ii ] [ MAPDIST ] end if heatmap [ ii ] [ MAPDIST ] < minMadeDist then minMadeDist = heatmap [ ii ] [ MAPDIST ] end else tUnmadeDist = tUnmadeDist + heatmap [ ii ] [ MAPDIST ] if heatmap [ ii ] [ MAPDIST ] > maxUnmadeDist then maxUnmadeDist = heatmap [ ii ] [ MAPDIST ] end if heatmap [ ii ] [ MAPDIST ] < minUnmadeDist then minUnmadeDist = heatmap [ ii ] [ MAPDIST ] end end end if madeTotal > 0 then print ( "minimum made contact distance = " .. r3 ( minMadeDist ) ) print ( "maximum made contact distance = " .. r3 ( maxMadeDist ) ) print ( "mean made contact distance = " .. r3 ( tMadeDist / madeTotal ) ) end if unmadeTotal > 0 then print ( "minimum unmade contact distance = " .. r3 ( minUnmadeDist ) ) print ( "maximum unmade contact distance = " .. r3 ( maxUnmadeDist ) ) print ( "mean unmade contact distance = " .. r3 ( tUnmadeDist / ( unmadeTotal ) ) ) end if minHeat == maxHeat then print ( "all contacts have heat = " .. r3 ( minHeat ) ) else print ( "minimum contact heat = " .. r3 ( minHeat ) ) print ( "maximum contact heat = " .. r3 ( maxHeat ) ) meanHeat = tHeat / #heatmap print ( "mean contact heat = " .. r3 ( meanHeat ) ) ii = math.floor ( #heatmap / 2 ) mediHeat = heatmap [ ii ] [ MAPHEAT ] if #heatmap % 2 == 0 then -- even number of entries ii = ii + 1 mediHeat = ( mediHeat + heatmap [ ii ] [ MAPHEAT ] ) / 2 -- corrected BK 04/06/2014 end print ( "median contact heat = " .. r3 ( mediHeat ) ) end end return heatmap end function GetParams ( contactMap, filterBonus ) local askresult = 0 repeat local dlg = dialog.CreateDialog ( ReVersion ) dlg.l000 = dialog.AddLabel ( "made contacts = " .. madeTotal ) dlg.l010 = dialog.AddLabel ( "filter bonus = " .. filterBonus ) dlg.l020 = dialog.AddLabel ( "unweighted contact bonus = " .. unweightedBonus ) dlg.l030 = dialog.AddLabel ( "weighted contact bonus = " .. weightedBonus ) -- -- tostring calls needed because recipe.CompareNumbers didn't do it, -- but neither did r3() -- local weighted = tostring ( weightedBonus ) local unweighted = tostring ( unweightedBonus ) local filtered = tostring ( filterBonus ) local matstr1 local matstr2 if unweighted == filtered then matstr1 = "unweighted scoring" matstr2 = "contact bonus is " .. madeTotal .. " * " .. contactBonus .. " = " .. unweightedBonus elseif weighted == filtered then matstr1 = "weighted contact scoring, total bonus is the sum of" matstr2 = contactBonus .. " * contact weight for each made contact" else matstr1 = "mismatch between filter bonus and calculated contact bonus" matstr2 = "try changing \'contact bonus\' and clicking \'recalc\'" end dlg.l040 = dialog.AddLabel ( matstr1 ) dlg.l050 = dialog.AddLabel ( matstr2 ) dlg.contactBonus = dialog.AddTextbox ( "contact bonus: ", contactBonus ) dlg.ok = dialog.AddButton ( "OK", 1 ) dlg.contacts = dialog.AddButton ( "Recalc", 2 ) dlg.cancel = dialog.AddButton ( "Cancel", 0 ) askresult = dialog.Show ( dlg ) if askresult > 0 then contactBonus = dlg.contactBonus.value end if askresult == 2 then unweightedBonus = 0 weightedBonus = 0 for ii = 1, #contactMap do if contactMap [ ii ] [ MAPCONT ] then contactMap [ ii ] [ MAPUBON ] = contactBonus contactMap [ ii ] [ MAPWBON ] = contactBonus * contactMap [ ii ] [ MAPHEAT ] unweightedBonus = unweightedBonus + contactMap [ ii ] [ MAPUBON ] weightedBonus = weightedBonus + contactMap [ ii ] [ MAPWBON ] end end end until askresult < 2 return askresult > 0 end function NoContacts () local ask = dialog.CreateDialog ( ReVersion .. " no contacts" ) ask.l15 = dialog.AddLabel ( "No contacts found." ) ask.l16 = dialog.AddLabel ( "This recipe is for contact map puzzles." ) ask.OK = dialog.AddButton ( "OK", 1 ) dialog.Show ( ask ) end function main () print ( ReVersion ) local heatmap = GetContacts () if #heatmap > 0 then print ( "total made contacts = " .. madeTotal ) local filterstate = behavior.GetFiltersDisabled () behavior.SetFiltersDisabled ( false ) local filtScore = current.GetEnergyScore () behavior.SetFiltersDisabled ( true ) local nofiltScore = current.GetEnergyScore () behavior.SetFiltersDisabled ( filterstate ) local filterBonus = filtScore - nofiltScore print ( "filter bonus = " .. r3 ( filterBonus ) ) print ( "default contact bonus (per contact) = " .. contactBonus ) print ( "total unweighted bonus = " .. unweightedBonus ) print ( "total weighted bonus = " .. weightedBonus ) if GetParams ( heatmap, filterBonus ) then end else NoContacts () end cleanup () end function cleanup ( error ) -- -- optionally, do not loop if cleanup causes an error -- (any loop here is automatically terminated after a few iterations, however) -- if CLEANUPENTRY ~= nil then return end CLEANUPENTRY = true print ( "---" ) local reason local start, stop, line, msg if error == nil then reason = "complete" else -- -- model 120 - civilized error reporting, -- thanks to Bruno K. and Jean-Bob -- start, stop, line, msg = error:find ( ":(%d+):%s()" ) if msg ~= nil then error = error:sub ( msg, #error ) end if error:find ( "Cancelled" ) ~= nil then reason = "cancelled" else reason = "error" end end -- -- model 100 - print recipe name, puzzle, track, time, score, and gain -- print ( ReVersion .. " " .. reason ) print ( "Puzzle: " .. puzzle.GetName () ) print ( "Track: " .. ui.GetTrackName () ) if reason == "error" then print ( "Unexpected error detected" ) print ( "Error line: " .. line ) print ( "Error: \"" .. error .. "\"" ) end -- -- model 130 - reset clash importance, clear selections, restore structures, etc. -- end xpcall ( main, cleanup )

Comments


LociOiling Lv 1

This recipe calculates the contact bonus and compares it the current total bonus.

The recipe uses 50 points as the base contact bonus.

Recent puzzles use a weighted contact bonus, where the bonus for making a contact is the base bonus times the weight or heat assigned to the contact. The weighted bonus for a contact with weight 1.0 would be 50 points, but it would be only 25 points for a contact with weight 0.5.

Older puzzles were in effect unweighted, since all contacts had weight 1.0.

You can adjust the points per contact if the calculated total doesn't match the current bonus.

Somehow, it doesn't seem fair that the lower-weighted contacts receive less bonus when made. Why should you receive 50 points for making contact with near neighbor but only 25 points for reaching some unlikely distant segment? But that's how it works, the numbers don't lie.

Bruno Kestemont Lv 1

It's indeed absurd to recive a 50 pts bonus for an probably inexistent contact (like false contacts to the neibourgh protein). This foldit "mistake" could prevent us to find the best "science" solution.