Class: HeatingSystemCalculator::ElementFinderByArea
- Inherits:
-
BaseElementFinder
- Object
- BaseItemFinder
- BaseElementFinder
- HeatingSystemCalculator::ElementFinderByArea
- Defined in:
- app/services/heating_system_calculator/element_finder_by_area.rb
Constant Summary collapse
- MAX_SOLUTIONS =
Limit solutions to avoid explosive memory allocation
50
Instance Attribute Summary collapse
-
#coverage_percentage ⇒ Object
readonly
Returns the value of attribute coverage_percentage.
-
#elements ⇒ Object
readonly
Returns the value of attribute elements.
-
#error ⇒ Object
readonly
Returns the value of attribute error.
-
#target_area ⇒ Object
readonly
Returns the value of attribute target_area.
Instance Method Summary collapse
- #calculate_elements_for_heated_area_sqft(heated_area_sqft, insulation_surface = nil) ⇒ Object
- #find_elements(tcmin, tcmax) ⇒ Object
- #find_solutions_for(num, amin, amax) ⇒ Object
-
#initialize(options) ⇒ ElementFinderByArea
constructor
A new instance of ElementFinderByArea.
Methods inherited from BaseElementFinder
#consolidate_elements, #find_best_solution
Methods inherited from BaseItemFinder
#get_number_of_poles_from_consolidated_elements, #get_total_from_solution
Constructor Details
#initialize(options) ⇒ ElementFinderByArea
Returns a new instance of ElementFinderByArea.
8 9 10 |
# File 'app/services/heating_system_calculator/element_finder_by_area.rb', line 8 def initialize() @element_set = [:element_set] end |
Instance Attribute Details
#coverage_percentage ⇒ Object (readonly)
Returns the value of attribute coverage_percentage.
3 4 5 |
# File 'app/services/heating_system_calculator/element_finder_by_area.rb', line 3 def coverage_percentage @coverage_percentage end |
#elements ⇒ Object (readonly)
Returns the value of attribute elements.
3 4 5 |
# File 'app/services/heating_system_calculator/element_finder_by_area.rb', line 3 def elements @elements end |
#error ⇒ Object (readonly)
Returns the value of attribute error.
3 4 5 |
# File 'app/services/heating_system_calculator/element_finder_by_area.rb', line 3 def error @error end |
#target_area ⇒ Object (readonly)
Returns the value of attribute target_area.
3 4 5 |
# File 'app/services/heating_system_calculator/element_finder_by_area.rb', line 3 def target_area @target_area end |
Instance Method Details
#calculate_elements_for_heated_area_sqft(heated_area_sqft, insulation_surface = nil) ⇒ Object
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 |
# File 'app/services/heating_system_calculator/element_finder_by_area.rb', line 12 def calculate_elements_for_heated_area_sqft(heated_area_sqft, insulation_surface = nil) # Priority: Find solutions that MEET OR EXCEED the heated area first # Only fall back to undersized solutions if nothing adequate can be found insulation_surface ||= heated_area_sqft # Phase 1: Try to find solutions that meet or exceed the target (up to insulation surface) # tcmin = target area, tcmax = insulation surface (allows slight overage) solutionset = find_elements(heated_area_sqft, insulation_surface) # Phase 2: If no adequate solutions, expand search to slightly over insulation surface if solutionset.length < MAX_SOLUTIONS solutionset.concat(find_elements(heated_area_sqft, 1.1 * insulation_surface)) end # Phase 3: If still no solutions, allow undersized (fallback only) if solutionset.empty? # For small areas, allow down to 46-90% coverage on sliding scale # For larger areas, allow down to 85% coverage if heated_area_sqft < 120.0 min_coverage = (0.46 + (0.44 * (heated_area_sqft / 120.0))) * heated_area_sqft else min_coverage = 0.85 * heated_area_sqft end solutionset = find_elements(min_coverage, heated_area_sqft) end foundsolution = false if solutionset.length > 1 # if we get more than a single solution, find the best based on closeness to target and price solution = find_best_solution(solutionset, heated_area_sqft) foundsolution = true elsif solutionset.length == 1 # if we get a single solution, we are done solution = solutionset[0] foundsolution = true end if foundsolution # get consolidated elements list @elements = consolidate_elements(solution) @coverage_percentage = [(100.0 * get_total_from_solution(solution, 'area').to_f / heated_area_sqft).round, 100].min else @error = { error_status: :no_solutions_found, error_message: 'No matching heating elements found.' } end @target_area = heated_area_sqft self end |
#find_elements(tcmin, tcmax) ⇒ Object
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 |
# File 'app/services/heating_system_calculator/element_finder_by_area.rb', line 60 def find_elements(tcmin, tcmax) # try to find a set of elements that at least covers TCMIN but does not exceed TCMAX # assume no set was found foundsolution = false # initialize the number of rolls to try in the solution set, guess a smaller than necessary but good starting point numrollstotry = [0, ((tcmin / @element_set.last['area']).floor - 1)].max # initialize the solution set solutionset = [] # Track total solutions found across all iterations @total_solutions_found = 0 # loop while no solution is found until foundsolution # increment the number of rolls to try numrollstotry += 1 # test to see if smallest roll times the number of rolls to try exceeds TCMAX if @element_set[0]['area'] * numrollstotry > tcmax # yes we are done, no more solutions possible break elsif numrollstotry > 100 # here we have a large area compared to our elements set so just set the solution to the correct set of the largest numrolls = ((tcmax / @element_set.last['area']).ceil - 1) soln = [] numrolls.times do soln << @element_set.last end solutionset << soln foundsolution = true else # keep looking for solutions using this number of rolls @recurse_count = 0 solutionset = find_solutions_for(numrollstotry, tcmin, tcmax) # trace("solutionset.length: "+String(solutionset.length)); foundsolution = true if solutionset.length >= 1 end end solutionset end |
#find_solutions_for(num, amin, amax) ⇒ Object
98 99 100 101 102 103 104 105 106 107 108 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 |
# File 'app/services/heating_system_calculator/element_finder_by_area.rb', line 98 def find_solutions_for(num, amin, amax) # this is a recursive function that enumerates all combinations of num rolls whose coverage # sums up to a value between amin and amax # set up an array of solutions solutionset = [] # Early termination if we have enough solutions globally return solutionset if @total_solutions_found >= MAX_SOLUTIONS if @recurse_count <= 100 if num == 1 # we are looking for a single roll solution # loop over all elements in the product set @element_set.each do |elem| break if @total_solutions_found >= MAX_SOLUTIONS # test that area of the roll is within range if elem['area'] >= amin && elem['area'] <= amax # save this roll as a solution solutionset << [elem] @total_solutions_found += 1 elsif elem['area'] > amax # we have reached a roll whose area exceeds TCMAX, we can stop looping # since the elements array is sorted ascending on area break end end elsif num > 1 # we are looking for more than a single roll solution @recurse_count += 1 # do a sanity check if @element_set[0]['area'] * num <= amax # loop over all elements @element_set.reverse_each do |elem| break if @total_solutions_found >= MAX_SOLUTIONS # Skip elements that are too large for the remaining area next if elem['area'] > amax # recursively call this function with a starting area of the current element roll # and num-1 rolls required tmpsolnset = find_solutions_for(num - 1, amin - elem['area'], amax - elem['area']) next if tmpsolnset.empty? # if the resulting solution set is non empty then add the current element roll to each solution in the set # Optimize: avoid creating intermediate arrays tmpsolnset.each do |tmpsol| break if @total_solutions_found >= MAX_SOLUTIONS solutionset << [elem, *tmpsol] @total_solutions_found += 1 end end end end end # return the solution set solutionset end |