liquid_feedback_core
changeset 302:548cec6b7a79
Better tie-breaking
author | jbe |
---|---|
date | Sat Sep 29 23:41:37 2012 +0200 (2012-09-29) |
parents | 7cad34b945ac |
children | 585c1cf4a3c9 |
files | core.sql test.sql |
line diff
1.1 --- a/core.sql Tue Sep 25 13:54:24 2012 +0200 1.2 +++ b/core.sql Sat Sep 29 23:41:37 2012 +0200 1.3 @@ -7,7 +7,7 @@ 1.4 BEGIN; 1.5 1.6 CREATE VIEW "liquid_feedback_version" AS 1.7 - SELECT * FROM (VALUES ('2.1.0', 2, 1, 0)) 1.8 + SELECT * FROM (VALUES ('2.2.0', 2, 2, 0)) 1.9 AS "subquery"("string", "major", "minor", "revision"); 1.10 1.11 1.12 @@ -329,6 +329,11 @@ 1.13 COMMENT ON COLUMN "session"."lang" IS 'Language code of the selected language'; 1.14 1.15 1.16 +CREATE TYPE "schulze_variant" AS ENUM ('absolute_strength', 'tuple_strength', 'partial_tie_breaking', 'tie_breaking_with_negative_strength'); 1.17 + 1.18 +COMMENT ON TYPE "schulze_variant" IS 'Variant of schulze method, which differ by complexity; greater values have a higher complexity'; 1.19 + 1.20 + 1.21 CREATE TABLE "policy" ( 1.22 "id" SERIAL4 PRIMARY KEY, 1.23 "index" INT4 NOT NULL, 1.24 @@ -356,6 +361,7 @@ 1.25 "indirect_majority_non_negative" INT4 NOT NULL DEFAULT 0, 1.26 "no_reverse_beat_path" BOOLEAN NOT NULL DEFAULT TRUE, 1.27 "no_multistage_majority" BOOLEAN NOT NULL DEFAULT FALSE, 1.28 + "schulze_variant" "schulze_variant" NOT NULL DEFAULT 'tie_breaking_with_negative_strength', 1.29 CONSTRAINT "timing" CHECK ( 1.30 ( "polling" = FALSE AND 1.31 "admission_time" NOTNULL AND "discussion_time" NOTNULL AND 1.32 @@ -671,13 +677,13 @@ 1.33 COMMENT ON COLUMN "initiative"."negative_votes" IS 'Calculated from table "direct_voter"'; 1.34 COMMENT ON COLUMN "initiative"."direct_majority" IS 'TRUE, if "positive_votes"/("positive_votes"+"negative_votes") is strictly greater or greater-equal than "direct_majority_num"/"direct_majority_den", and "positive_votes" is greater-equal than "direct_majority_positive", and ("positive_votes"+abstentions) is greater-equal than "direct_majority_non_negative"'; 1.35 COMMENT ON COLUMN "initiative"."indirect_majority" IS 'Same as "direct_majority", but also considering indirect beat paths'; 1.36 -COMMENT ON COLUMN "initiative"."schulze_rank" IS 'Schulze-Ranking without tie-breaking'; 1.37 -COMMENT ON COLUMN "initiative"."better_than_status_quo" IS 'TRUE, if initiative has a schulze-ranking better than the status quo (without tie-breaking)'; 1.38 -COMMENT ON COLUMN "initiative"."worse_than_status_quo" IS 'TRUE, if initiative has a schulze-ranking worse than the status quo (without tie-breaking)'; 1.39 +COMMENT ON COLUMN "initiative"."schulze_rank" IS 'Schulze-Ranking'; 1.40 +COMMENT ON COLUMN "initiative"."better_than_status_quo" IS 'TRUE, if initiative has a schulze-ranking better than the status quo'; 1.41 +COMMENT ON COLUMN "initiative"."worse_than_status_quo" IS 'TRUE, if initiative has a schulze-ranking worse than the status quo'; 1.42 COMMENT ON COLUMN "initiative"."reverse_beat_path" IS 'TRUE, if there is a beat path (may include ties) from this initiative to the status quo'; 1.43 COMMENT ON COLUMN "initiative"."multistage_majority" IS 'TRUE, if either (a) this initiative has no better rank than the status quo, or (b) there exists a better ranked initiative X, which directly beats this initiative, and either more voters prefer X to this initiative than voters preferring X to the status quo or less voters prefer this initiative to X than voters preferring the status quo to X'; 1.44 COMMENT ON COLUMN "initiative"."eligible" IS 'Initiative has a "direct_majority" and an "indirect_majority", is "better_than_status_quo" and depending on selected policy the initiative has no "reverse_beat_path" or "multistage_majority"'; 1.45 -COMMENT ON COLUMN "initiative"."winner" IS 'Winner is the "eligible" initiative with best "schulze_rank" and in case of ties with lowest "id"'; 1.46 +COMMENT ON COLUMN "initiative"."winner" IS 'Winner is the "eligible" initiative with best "schulze_rank" and in case of unresolved ties with lowest "id"'; 1.47 COMMENT ON COLUMN "initiative"."rank" IS 'Unique ranking for all "admitted" initiatives per issue; lower rank is better; a winner always has rank 1, but rank 1 does not imply that an initiative is winner; initiatives with "direct_majority" AND "indirect_majority" always have a better (lower) rank than other initiatives'; 1.48 1.49 1.50 @@ -3848,22 +3854,47 @@ 1.51 IS 'Closes the voting on an issue, and calculates positive and negative votes for each initiative; The ranking is not calculated yet, to keep the (locking) transaction short.'; 1.52 1.53 1.54 +CREATE FUNCTION "impossible_defeat_strength"() 1.55 + RETURNS INT8 1.56 + LANGUAGE 'plpgsql' IMMUTABLE AS $$ 1.57 + BEGIN 1.58 + RETURN -1::INT8 << 63; 1.59 + END; 1.60 + $$; 1.61 + 1.62 +COMMENT ON FUNCTION "impossible_defeat_strength"() IS 'An INT8 value lower than any other value returned by function "defeat_strength"(INT4, INT4)'; 1.63 + 1.64 + 1.65 CREATE FUNCTION "defeat_strength" 1.66 - ( "positive_votes_p" INT4, "negative_votes_p" INT4 ) 1.67 + ( "schulze_variant_p" "schulze_variant", 1.68 + "positive_votes_p" INT4, 1.69 + "negative_votes_p" INT4 ) 1.70 RETURNS INT8 1.71 LANGUAGE 'plpgsql' IMMUTABLE AS $$ 1.72 BEGIN 1.73 - IF "positive_votes_p" > "negative_votes_p" THEN 1.74 - RETURN ("positive_votes_p"::INT8 << 31) - "negative_votes_p"::INT8; 1.75 - ELSIF "positive_votes_p" = "negative_votes_p" THEN 1.76 - RETURN 0; 1.77 + IF "schulze_variant_p" >= 'tuple_strength'::"schulze_variant" THEN 1.78 + IF "positive_votes_p" > "negative_votes_p" THEN 1.79 + RETURN ("positive_votes_p"::INT8 << 31) - "negative_votes_p"::INT8; 1.80 + ELSIF "positive_votes_p" = "negative_votes_p" THEN 1.81 + RETURN 0::INT8; 1.82 + ELSE 1.83 + IF "schulze_variant_p" >= 'tie_breaking_with_negative_strength'::"schulze_variant" THEN 1.84 + RETURN "positive_votes_p"::INT8 - ("negative_votes_p"::INT8 << 31); 1.85 + ELSE 1.86 + RETURN "impossible_defeat_strength"(); 1.87 + END IF; 1.88 + END IF; 1.89 ELSE 1.90 - RETURN -1; 1.91 + IF "positive_votes_p" > "negative_votes_p" THEN 1.92 + RETURN "positive_votes_p"::INT8; 1.93 + ELSE 1.94 + RETURN 0::INT8; 1.95 + END IF; 1.96 END IF; 1.97 END; 1.98 $$; 1.99 1.100 -COMMENT ON FUNCTION "defeat_strength"(INT4, INT4) IS 'Calculates defeat strength (INT8!) of a pairwise defeat primarily by the absolute number of votes for the winner and secondarily by the absolute number of votes for the loser'; 1.101 +COMMENT ON FUNCTION "defeat_strength"("schulze_variant", INT4, INT4) IS 'Calculates defeat strength (INT8!) of a pairwise defeat primarily by the absolute number of votes for the winner and secondarily by the absolute number of votes for the loser. A tie has a strength of zero.'; 1.102 1.103 1.104 CREATE FUNCTION "calculate_ranks"("issue_id_p" "issue"."id"%TYPE) 1.105 @@ -3873,11 +3904,17 @@ 1.106 "issue_row" "issue"%ROWTYPE; 1.107 "policy_row" "policy"%ROWTYPE; 1.108 "dimension_v" INTEGER; 1.109 - "vote_matrix" INT4[][]; -- absolute votes 1.110 - "matrix" INT8[][]; -- defeat strength / best paths 1.111 + "vote_matrix" INT4[][]; -- absolute votes 1.112 + "direct_matrix" INT8[][]; -- direct defeat strength 1.113 + "path_matrix" INT8[][]; -- defeat strength of best path 1.114 + "compare_matrix" BOOLEAN[][]; -- binary relation to create schulze-rank 1.115 + "forbidden_matrix" BOOLEAN[][]; -- forbidden links for tie-breaking 1.116 + "modified_matrix" INT8[][]; -- defeat strength after tie-breaking 1.117 "i" INTEGER; 1.118 "j" INTEGER; 1.119 "k" INTEGER; 1.120 + "m" INTEGER; 1.121 + "n" INTEGER; 1.122 "battle_row" "battle"%ROWTYPE; 1.123 "rank_ary" INT4[]; 1.124 "rank_v" INT4; 1.125 @@ -3917,15 +3954,16 @@ 1.126 IF "i" != "dimension_v" OR "j" != "dimension_v" + 1 THEN 1.127 RAISE EXCEPTION 'Wrong battle count (should not happen)'; 1.128 END IF; 1.129 - -- Store defeat strengths in "matrix" using "defeat_strength" 1.130 + -- Store defeat strengths in "path_matrix" using "defeat_strength" 1.131 -- function: 1.132 - "matrix" := array_fill(NULL::INT8, ARRAY["dimension_v", "dimension_v"]); 1.133 + "direct_matrix" := array_fill(NULL::INT8, ARRAY["dimension_v", "dimension_v"]); 1.134 "i" := 1; 1.135 LOOP 1.136 "j" := 1; 1.137 LOOP 1.138 IF "i" != "j" THEN 1.139 - "matrix"["i"]["j"] := "defeat_strength"( 1.140 + "direct_matrix"["i"]["j"] := "defeat_strength"( 1.141 + "policy_row"."schulze_variant", 1.142 "vote_matrix"["i"]["j"], 1.143 "vote_matrix"["j"]["i"] 1.144 ); 1.145 @@ -3937,6 +3975,7 @@ 1.146 "i" := "i" + 1; 1.147 END LOOP; 1.148 -- Find best paths: 1.149 + "path_matrix" := "direct_matrix"; 1.150 "i" := 1; 1.151 LOOP 1.152 "j" := 1; 1.153 @@ -3945,13 +3984,13 @@ 1.154 "k" := 1; 1.155 LOOP 1.156 IF "i" != "k" AND "j" != "k" THEN 1.157 - IF "matrix"["j"]["i"] < "matrix"["i"]["k"] THEN 1.158 - IF "matrix"["j"]["i"] > "matrix"["j"]["k"] THEN 1.159 - "matrix"["j"]["k"] := "matrix"["j"]["i"]; 1.160 + IF "path_matrix"["j"]["i"] < "path_matrix"["i"]["k"] THEN 1.161 + IF "path_matrix"["j"]["i"] > "path_matrix"["j"]["k"] THEN 1.162 + "path_matrix"["j"]["k"] := "path_matrix"["j"]["i"]; 1.163 END IF; 1.164 ELSE 1.165 - IF "matrix"["i"]["k"] > "matrix"["j"]["k"] THEN 1.166 - "matrix"["j"]["k"] := "matrix"["i"]["k"]; 1.167 + IF "path_matrix"["i"]["k"] > "path_matrix"["j"]["k"] THEN 1.168 + "path_matrix"["j"]["k"] := "path_matrix"["i"]["k"]; 1.169 END IF; 1.170 END IF; 1.171 END IF; 1.172 @@ -3965,6 +4004,131 @@ 1.173 EXIT WHEN "i" = "dimension_v"; 1.174 "i" := "i" + 1; 1.175 END LOOP; 1.176 + -- initial calculation of binary relation: 1.177 + "compare_matrix" := array_fill(NULL::BOOLEAN, ARRAY["dimension_v", "dimension_v"]); 1.178 + "i" := 1; 1.179 + LOOP 1.180 + "j" := 1; 1.181 + LOOP 1.182 + IF "i" != "j" THEN 1.183 + IF "path_matrix"["i"]["j"] > "path_matrix"["j"]["i"] THEN 1.184 + "compare_matrix"["i"]["j"] = TRUE; 1.185 + "compare_matrix"["j"]["i"] = FALSE; 1.186 + ELSIF "path_matrix"["i"]["j"] < "path_matrix"["j"]["i"] THEN 1.187 + "compare_matrix"["i"]["j"] = FALSE; 1.188 + "compare_matrix"["j"]["i"] = TRUE; 1.189 + END IF; 1.190 + END IF; 1.191 + EXIT WHEN "j" = "dimension_v"; 1.192 + "j" := "j" + 1; 1.193 + END LOOP; 1.194 + EXIT WHEN "i" = "dimension_v"; 1.195 + "i" := "i" + 1; 1.196 + END LOOP; 1.197 + -- tie-breaking: 1.198 + IF "policy_row"."schulze_variant" >= 'partial_tie_breaking'::"schulze_variant" THEN 1.199 + "modified_matrix" := array_fill(NULL::INT8, ARRAY["dimension_v", "dimension_v"]); 1.200 + "forbidden_matrix" := array_fill(NULL::BOOLEAN, ARRAY["dimension_v", "dimension_v"]); 1.201 + "m" := 1; 1.202 + LOOP 1.203 + "n" := "m" + 1; 1.204 + LOOP 1.205 + IF "compare_matrix"["m"]["n"] ISNULL THEN 1.206 + "i" := 1; 1.207 + LOOP 1.208 + "j" := 1; 1.209 + LOOP 1.210 + IF "i" != "j" THEN 1.211 + "forbidden_matrix"["i"]["j"] := FALSE; 1.212 + "modified_matrix"["i"]["j"] := "path_matrix"["i"]["j"]; 1.213 + END IF; 1.214 + EXIT WHEN "j" = "dimension_v"; 1.215 + "j" := "j" + 1; 1.216 + END LOOP; 1.217 + EXIT WHEN "i" = "dimension_v"; 1.218 + "i" := "i" + 1; 1.219 + END LOOP; 1.220 + LOOP 1.221 + "i" := 1; 1.222 + LOOP 1.223 + "j" := 1; 1.224 + LOOP 1.225 + IF "i" != "j" THEN 1.226 + IF "modified_matrix"["m"]["n"] = "direct_matrix"["i"]["j"] THEN 1.227 + "forbidden_matrix"["i"]["j"] := TRUE; 1.228 + END IF; 1.229 + END IF; 1.230 + EXIT WHEN "j" = "dimension_v"; 1.231 + "j" := "j" + 1; 1.232 + END LOOP; 1.233 + EXIT WHEN "i" = "dimension_v"; 1.234 + "i" := "i" + 1; 1.235 + END LOOP; 1.236 + "i" := 1; 1.237 + LOOP 1.238 + "j" := 1; 1.239 + LOOP 1.240 + IF "i" != "j" THEN 1.241 + IF "forbidden_matrix"["i"]["j"] THEN 1.242 + "modified_matrix"["i"]["j"] := "impossible_defeat_strength"(); 1.243 + ELSE 1.244 + "modified_matrix"["i"]["j"] := "direct_matrix"["i"]["j"]; 1.245 + END IF; 1.246 + END IF; 1.247 + EXIT WHEN "j" = "dimension_v"; 1.248 + "j" := "j" + 1; 1.249 + END LOOP; 1.250 + EXIT WHEN "i" = "dimension_v"; 1.251 + "i" := "i" + 1; 1.252 + END LOOP; 1.253 + "i" := 1; 1.254 + LOOP 1.255 + "j" := 1; 1.256 + LOOP 1.257 + IF "i" != "j" THEN 1.258 + "k" := 1; 1.259 + LOOP 1.260 + IF "i" != "k" AND "j" != "k" THEN 1.261 + IF "modified_matrix"["j"]["i"] < "modified_matrix"["i"]["k"] THEN 1.262 + IF "modified_matrix"["j"]["i"] > "modified_matrix"["j"]["k"] THEN 1.263 + "modified_matrix"["j"]["k"] := "modified_matrix"["j"]["i"]; 1.264 + END IF; 1.265 + ELSE 1.266 + IF "modified_matrix"["i"]["k"] > "modified_matrix"["j"]["k"] THEN 1.267 + "modified_matrix"["j"]["k"] := "modified_matrix"["i"]["k"]; 1.268 + END IF; 1.269 + END IF; 1.270 + END IF; 1.271 + EXIT WHEN "k" = "dimension_v"; 1.272 + "k" := "k" + 1; 1.273 + END LOOP; 1.274 + END IF; 1.275 + EXIT WHEN "j" = "dimension_v"; 1.276 + "j" := "j" + 1; 1.277 + END LOOP; 1.278 + EXIT WHEN "i" = "dimension_v"; 1.279 + "i" := "i" + 1; 1.280 + END LOOP; 1.281 + IF "modified_matrix"["m"]["n"] > "modified_matrix"["n"]["m"] THEN 1.282 + "compare_matrix"["m"]["n"] := TRUE; 1.283 + "compare_matrix"["n"]["m"] := FALSE; 1.284 + EXIT; 1.285 + ELSIF "modified_matrix"["m"]["n"] < "modified_matrix"["n"]["m"] THEN 1.286 + "compare_matrix"["m"]["n"] := FALSE; 1.287 + "compare_matrix"["n"]["m"] := TRUE; 1.288 + EXIT; 1.289 + ELSIF "modified_matrix"["m"]["n"] = "impossible_defeat_strength"() THEN 1.290 + EXIT; 1.291 + END IF; 1.292 + END LOOP; 1.293 + END IF; 1.294 + EXIT WHEN "n" = "dimension_v"; 1.295 + "n" := "n" + 1; 1.296 + END LOOP; 1.297 + EXIT WHEN "m" = "dimension_v" - 1; 1.298 + "m" := "m" + 1; 1.299 + END LOOP; 1.300 + END IF; 1.301 -- Determine order of winners: 1.302 "rank_ary" := array_fill(NULL::INT4, ARRAY["dimension_v"]); 1.303 "rank_v" := 1; 1.304 @@ -3979,7 +4143,7 @@ 1.305 IF 1.306 "i" != "j" AND 1.307 "rank_ary"["j"] ISNULL AND 1.308 - "matrix"["j"]["i"] > "matrix"["i"]["j"] 1.309 + "compare_matrix"["j"]["i"] 1.310 THEN 1.311 -- someone else is better 1.312 EXIT; 1.313 @@ -4039,7 +4203,7 @@ 1.314 "better_than_status_quo" = "rank_ary"["i"] < "rank_ary"["dimension_v"], 1.315 "worse_than_status_quo" = "rank_ary"["i"] > "rank_ary"["dimension_v"], 1.316 "multistage_majority" = "rank_ary"["i"] >= "rank_ary"["dimension_v"], 1.317 - "reverse_beat_path" = "matrix"["dimension_v"]["i"] >= 0, 1.318 + "reverse_beat_path" = "path_matrix"["dimension_v"]["i"] >= 0, 1.319 "eligible" = FALSE, 1.320 "winner" = FALSE, 1.321 "rank" = NULL -- NOTE: in cases of manual reset of issue state
2.1 --- a/test.sql Tue Sep 25 13:54:24 2012 +0200 2.2 +++ b/test.sql Sat Sep 29 23:41:37 2012 +0200 2.3 @@ -41,7 +41,8 @@ 2.4 "issue_quorum_num", "issue_quorum_den", 2.5 "initiative_quorum_num", "initiative_quorum_den", 2.6 "direct_majority_num", "direct_majority_den", "direct_majority_strict", 2.7 - "no_reverse_beat_path", "no_multistage_majority" 2.8 + "no_reverse_beat_path", "no_multistage_majority", 2.9 + "schulze_variant" 2.10 ) VALUES ( 2.11 1, 2.12 'Default policy', 2.13 @@ -49,7 +50,8 @@ 2.14 25, 100, 2.15 20, 100, 2.16 1, 2, TRUE, 2.17 - TRUE, FALSE ); 2.18 + TRUE, FALSE, 2.19 + 'tie_breaking_with_negative_strength'::"schulze_variant" ); 2.20 2.21 CREATE FUNCTION "time_warp"() RETURNS VOID 2.22 LANGUAGE 'plpgsql' VOLATILE AS $$ 2.23 @@ -118,10 +120,37 @@ 2.24 (18, 'unit', 1, 19), 2.25 (23, 'unit', 1, 22); 2.26 2.27 +-- no delegations in area #1 2.28 +INSERT INTO "delegation" 2.29 + ("truster_id", "scope", "area_id", "trustee_id") VALUES 2.30 + ( 1, 'area', 1, NULL), 2.31 + ( 2, 'area', 1, NULL), 2.32 + ( 3, 'area', 1, NULL), 2.33 + ( 4, 'area', 1, NULL), 2.34 + ( 5, 'area', 1, NULL), 2.35 + ( 6, 'area', 1, NULL), 2.36 + ( 7, 'area', 1, NULL), 2.37 + ( 8, 'area', 1, NULL), 2.38 + ( 9, 'area', 1, NULL), 2.39 + (10, 'area', 1, NULL), 2.40 + (11, 'area', 1, NULL), 2.41 + (12, 'area', 1, NULL), 2.42 + (13, 'area', 1, NULL), 2.43 + (14, 'area', 1, NULL), 2.44 + (15, 'area', 1, NULL), 2.45 + (16, 'area', 1, NULL), 2.46 + (17, 'area', 1, NULL), 2.47 + (18, 'area', 1, NULL), 2.48 + (19, 'area', 1, NULL), 2.49 + (20, 'area', 1, NULL), 2.50 + (21, 'area', 1, NULL), 2.51 + (22, 'area', 1, NULL), 2.52 + (23, 'area', 1, NULL); 2.53 + 2.54 -- delegations for topics 2.55 INSERT INTO "delegation" 2.56 ("area_id", "truster_id", "scope", "trustee_id") VALUES 2.57 - (1, 3, 'area', 17), 2.58 + --(1, 3, 'area', 17), 2.59 (2, 5, 'area', 10), 2.60 (2, 9, 'area', 10), 2.61 (3, 4, 'area', 14), 2.62 @@ -236,6 +265,25 @@ 2.63 (6, 9, 9), 2.64 (6, 10, 10), 2.65 (6, 11, 11); 2.66 + 2.67 +INSERT INTO "issue" ("area_id", "policy_id") VALUES 2.68 + (1, 1); -- id 3 2.69 + 2.70 +INSERT INTO "initiative" ("issue_id", "name") VALUES 2.71 + (3, 'First initiative'), -- id 12 2.72 + (3, 'Second initiative'); -- id 13 2.73 + 2.74 +INSERT INTO "draft" ("initiative_id", "author_id", "content") VALUES 2.75 + (12, 1, 'Lorem ipsum...'), -- id 12 2.76 + (13, 2, 'Lorem ipsum...'); -- id 13 2.77 + 2.78 +INSERT INTO "initiator" ("initiative_id", "member_id") VALUES 2.79 + (12, 1), 2.80 + (13, 2); 2.81 + 2.82 +INSERT INTO "supporter" ("initiative_id", "member_id") VALUES 2.83 + (12, 1), 2.84 + (13, 2); 2.85 2.86 SELECT "time_warp"(); 2.87 SELECT "time_warp"(); 2.88 @@ -402,6 +450,25 @@ 2.89 (20, 2, 10, -1), 2.90 (20, 2, 11, 3); 2.91 2.92 +INSERT INTO "direct_voter" ("member_id", "issue_id") VALUES 2.93 + ( 1, 3), 2.94 + ( 2, 3), 2.95 + ( 3, 3), 2.96 + ( 4, 3), 2.97 + ( 5, 3); 2.98 + 2.99 +INSERT INTO "vote" ("member_id", "issue_id", "initiative_id", "grade") VALUES 2.100 + (1, 3, 12, 1), 2.101 + (1, 3, 13, 1), 2.102 + (2, 3, 12, 1), 2.103 + (2, 3, 13, 1), 2.104 + (3, 3, 12, 0), 2.105 + (3, 3, 13, 1), 2.106 + (4, 3, 12, 0), 2.107 + (4, 3, 13, -1), 2.108 + (5, 3, 12, -1), 2.109 + (5, 3, 13, -1); 2.110 + 2.111 SELECT "time_warp"(); 2.112 2.113 DROP FUNCTION "time_warp"();