nodes: 0, module; 1, function_definition; 2, function_name:most_similar_catchments; 3, parameters; 4, block; 5, identifier:self; 6, identifier:subject_catchment; 7, identifier:similarity_dist_function; 8, default_parameter; 9, default_parameter; 10, expression_statement; 11, if_statement; 12, expression_statement; 13, comment:# At least 10 AMAX records; 14, expression_statement; 15, comment:# Add subject catchment if required (may not exist in database, so add after querying db; 16, if_statement; 17, comment:# Store the similarity distance as an additional attribute for each catchment; 18, for_statement; 19, comment:# Then simply sort by this attribute; 20, expression_statement; 21, comment:# Limit catchments until total amax_records counts is at least `records_limit`, default 500; 22, expression_statement; 23, expression_statement; 24, for_statement; 25, return_statement; 26, identifier:records_limit; 27, integer:500; 28, identifier:include_subject_catchment; 29, string; 30, comment:"""
          Return a list of catchments sorted by hydrological similarity defined by `similarity_distance_function`
  
          :param subject_catchment: subject catchment to find similar catchments for
          :type subject_catchment: :class:`floodestimation.entities.Catchment`
          :param similarity_dist_function: a method returning a similarity distance measure with 2 arguments, both
                                           :class:`floodestimation.entities.Catchment` objects
          :param include_subject_catchment: - `auto`: include subject catchment if suitable for pooling and if urbext < 0.03
                                            - `force`: always include subject catchment having at least 10 years of data
                                            - `exclude`: do not include the subject catchment
          :type include_subject_catchment: str
          :return: list of catchments sorted by similarity
          :type: list of :class:`floodestimation.entities.Catchment`
          """; 31, comparison_operator:include_subject_catchment not in ['auto', 'force', 'exclude']; 32, block; 33, assignment; 34, assignment; 35, comparison_operator:include_subject_catchment == 'force'; 36, block; 37, elif_clause; 38, identifier:catchment; 39, identifier:catchments; 40, block; 41, call; 42, assignment; 43, assignment; 44, identifier:catchment; 45, identifier:catchments; 46, block; 47, identifier:catchments_limited; 48, string_content:auto; 49, identifier:include_subject_catchment; 50, list; 51, raise_statement; 52, identifier:query; 53, parenthesized_expression; 54, identifier:catchments; 55, call; 56, identifier:include_subject_catchment; 57, string; 58, if_statement; 59, comparison_operator:include_subject_catchment == 'auto'; 60, block; 61, expression_statement; 62, attribute; 63, argument_list; 64, identifier:amax_records_count; 65, integer:0; 66, identifier:catchments_limited; 67, list; 68, expression_statement; 69, expression_statement; 70, if_statement; 71, string; 72, string; 73, string; 74, call; 75, call; 76, attribute; 77, argument_list; 78, string_content:force; 79, comparison_operator:len(subject_catchment.amax_records) >= 10; 80, comment:# Never include short-record catchments; 81, block; 82, identifier:include_subject_catchment; 83, string; 84, if_statement; 85, assignment; 86, identifier:catchments; 87, identifier:sort; 88, keyword_argument; 89, call; 90, augmented_assignment; 91, comparison_operator:amax_records_count >= records_limit; 92, block; 93, string_content:auto; 94, string_content:force; 95, string_content:exclude; 96, identifier:ValueError; 97, argument_list; 98, attribute; 99, argument_list; 100, identifier:query; 101, identifier:all; 102, call; 103, integer:10; 104, expression_statement; 105, string_content:auto; 106, boolean_operator; 107, block; 108, attribute; 109, call; 110, identifier:key; 111, call; 112, attribute; 113, argument_list; 114, identifier:amax_records_count; 115, attribute; 116, identifier:amax_records_count; 117, identifier:records_limit; 118, break_statement; 119, binary_operator:"Parameter `include_subject_catchment={}` invalid.".format(include_subject_catchment) +
                               "Must be one of `auto`, `force` or `exclude`."; 120, call; 121, identifier:having; 122, comparison_operator:func.count(AmaxRecord.catchment_id) >= 10; 123, identifier:len; 124, argument_list; 125, call; 126, boolean_operator; 127, line_continuation:\; 128, parenthesized_expression; 129, expression_statement; 130, identifier:catchment; 131, identifier:similarity_dist; 132, identifier:similarity_dist_function; 133, argument_list; 134, identifier:attrgetter; 135, argument_list; 136, identifier:catchments_limited; 137, identifier:append; 138, identifier:catchment; 139, identifier:catchment; 140, identifier:record_length; 141, call; 142, string:"Must be one of `auto`, `force` or `exclude`."; 143, attribute; 144, argument_list; 145, call; 146, integer:10; 147, attribute; 148, attribute; 149, argument_list; 150, comparison_operator:len(subject_catchment.amax_records) >= 10; 151, attribute; 152, boolean_operator; 153, call; 154, identifier:subject_catchment; 155, identifier:catchment; 156, string; 157, attribute; 158, argument_list; 159, call; 160, identifier:group_by; 161, identifier:Catchment; 162, attribute; 163, argument_list; 164, identifier:subject_catchment; 165, identifier:amax_records; 166, identifier:catchments; 167, identifier:append; 168, identifier:subject_catchment; 169, call; 170, integer:10; 171, identifier:subject_catchment; 172, identifier:is_suitable_for_pooling; 173, comparison_operator:subject_catchment.descriptors.urbext2000 < 0.03; 174, comparison_operator:subject_catchment.descriptors.urbext2000 is None; 175, attribute; 176, argument_list; 177, string_content:similarity_dist; 178, string:"Parameter `include_subject_catchment={}` invalid."; 179, identifier:format; 180, identifier:include_subject_catchment; 181, attribute; 182, argument_list; 183, identifier:func; 184, identifier:count; 185, attribute; 186, identifier:len; 187, argument_list; 188, attribute; 189, float:0.03; 190, attribute; 191, None; 192, identifier:catchments; 193, identifier:append; 194, identifier:subject_catchment; 195, call; 196, identifier:filter; 197, comparison_operator:Catchment.id != subject_catchment.id; 198, attribute; 199, call; 200, comparison_operator:AmaxRecord.flag == 0; 201, identifier:AmaxRecord; 202, identifier:catchment_id; 203, attribute; 204, attribute; 205, identifier:urbext2000; 206, attribute; 207, identifier:urbext2000; 208, attribute; 209, argument_list; 210, attribute; 211, attribute; 212, identifier:Catchment; 213, identifier:is_suitable_for_pooling; 214, identifier:or_; 215, argument_list; 216, attribute; 217, integer:0; 218, identifier:subject_catchment; 219, identifier:amax_records; 220, identifier:subject_catchment; 221, identifier:descriptors; 222, identifier:subject_catchment; 223, identifier:descriptors; 224, call; 225, identifier:join; 226, attribute; 227, identifier:Catchment; 228, identifier:id; 229, identifier:subject_catchment; 230, identifier:id; 231, comparison_operator:Descriptors.urbext2000 < 0.03; 232, comparison_operator:Descriptors.urbext2000 == None; 233, identifier:AmaxRecord; 234, identifier:flag; 235, attribute; 236, argument_list; 237, identifier:Catchment; 238, identifier:amax_records; 239, attribute; 240, float:0.03; 241, attribute; 242, None; 243, call; 244, identifier:join; 245, attribute; 246, identifier:Descriptors; 247, identifier:urbext2000; 248, identifier:Descriptors; 249, identifier:urbext2000; 250, attribute; 251, argument_list; 252, identifier:Catchment; 253, identifier:descriptors; 254, attribute; 255, identifier:query; 256, identifier:Catchment; 257, identifier:self; 258, identifier:db_session
code: def most_similar_catchments(self, subject_catchment, similarity_dist_function, records_limit=500,
                                  include_subject_catchment='auto'):
          """
          Return a list of catchments sorted by hydrological similarity defined by `similarity_distance_function`
  
          :param subject_catchment: subject catchment to find similar catchments for
          :type subject_catchment: :class:`floodestimation.entities.Catchment`
          :param similarity_dist_function: a method returning a similarity distance measure with 2 arguments, both
                                           :class:`floodestimation.entities.Catchment` objects
          :param include_subject_catchment: - `auto`: include subject catchment if suitable for pooling and if urbext < 0.03
                                            - `force`: always include subject catchment having at least 10 years of data
                                            - `exclude`: do not include the subject catchment
          :type include_subject_catchment: str
          :return: list of catchments sorted by similarity
          :type: list of :class:`floodestimation.entities.Catchment`
          """
          if include_subject_catchment not in ['auto', 'force', 'exclude']:
              raise ValueError("Parameter `include_subject_catchment={}` invalid.".format(include_subject_catchment) +
                               "Must be one of `auto`, `force` or `exclude`.")
  
          query = (self.db_session.query(Catchment).
                   join(Catchment.descriptors).
                   join(Catchment.amax_records).
                   filter(Catchment.id != subject_catchment.id,
                          Catchment.is_suitable_for_pooling,
                          or_(Descriptors.urbext2000 < 0.03, Descriptors.urbext2000 == None),
                          AmaxRecord.flag == 0).
                   group_by(Catchment).
                   having(func.count(AmaxRecord.catchment_id) >= 10))  # At least 10 AMAX records
          catchments = query.all()
  
          # Add subject catchment if required (may not exist in database, so add after querying db
          if include_subject_catchment == 'force':
              if len(subject_catchment.amax_records) >= 10:  # Never include short-record catchments
                  catchments.append(subject_catchment)
          elif include_subject_catchment == 'auto':
              if len(subject_catchment.amax_records) >= 10 and subject_catchment.is_suitable_for_pooling and \
                 (subject_catchment.descriptors.urbext2000 < 0.03 or subject_catchment.descriptors.urbext2000 is None):
                  catchments.append(subject_catchment)
  
          # Store the similarity distance as an additional attribute for each catchment
          for catchment in catchments:
              catchment.similarity_dist = similarity_dist_function(subject_catchment, catchment)
          # Then simply sort by this attribute
          catchments.sort(key=attrgetter('similarity_dist'))
  
          # Limit catchments until total amax_records counts is at least `records_limit`, default 500
          amax_records_count = 0
          catchments_limited = []
          for catchment in catchments:
              catchments_limited.append(catchment)
              amax_records_count += catchment.record_length
              if amax_records_count >= records_limit:
                  break
  
          return catchments_limited