Skip to content

Commit 06e1923

Browse files
manuelcandalesfacebook-github-bot
authored andcommitted
InputGen: introduce variable generator
Summary: A variable generator needs to be initialized with a variable space. It does the job of generating values from that variable space. Reviewed By: SS-JIA Differential Revision: D51816909 fbshipit-source-id: 55254ed5c37cc66c4bc8746de24ed47c2abd49aa
1 parent 3ba6b08 commit 06e1923

File tree

4 files changed

+392
-2
lines changed

4 files changed

+392
-2
lines changed

inputgen/variable/constants.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
# This source code is licensed under the license found in the
55
# LICENSE file in the root directory of this source tree.
66

7-
INT64_MAX = 2**63 - 1
8-
INT64_MIN = -(2**63)
7+
INT64_MAX = 9223372036854775807
8+
INT64_MIN = -9223372036854775808
9+
BOUND_ON_INF = 8

inputgen/variable/gen.py

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
import math
8+
import random
9+
from typing import Any, List, Optional, Set, Union
10+
11+
from inputgen.variable.constants import BOUND_ON_INF, INT64_MAX, INT64_MIN
12+
from inputgen.variable.space import Interval, Intervals, VariableSpace
13+
from inputgen.variable.utils import nextdown, nextup
14+
15+
16+
def gen_min_float_from_interval(r: Interval) -> Optional[float]:
17+
if r.empty():
18+
return None
19+
if not r.lower_open:
20+
return float(r.lower)
21+
else:
22+
next_float = nextup(r.lower)
23+
if next_float < r.upper or next_float == r.upper and not r.upper_open:
24+
return float(next_float)
25+
return None
26+
27+
28+
def gen_max_float_from_interval(r: Interval) -> Optional[float]:
29+
if r.empty():
30+
raise ValueError("interval must not be empty")
31+
if not r.upper_open:
32+
return float(r.upper)
33+
else:
34+
prev_float = nextdown(r.upper)
35+
if prev_float > r.lower or prev_float == r.lower and not r.lower_open:
36+
return float(prev_float)
37+
return None
38+
39+
40+
def gen_float_from_interval(r: Interval) -> Optional[float]:
41+
if r.empty():
42+
return None
43+
lower = gen_min_float_from_interval(r)
44+
upper = gen_max_float_from_interval(r)
45+
if lower == -math.inf:
46+
lower = nextup(lower)
47+
if upper == math.inf:
48+
upper = nextdown(upper)
49+
if lower is None or upper is None:
50+
return None
51+
elif lower > upper:
52+
return None
53+
else:
54+
return random.uniform(lower, upper)
55+
56+
57+
def gen_min_float_from_intervals(rs: Intervals) -> Optional[float]:
58+
if rs.empty():
59+
return None
60+
return gen_min_float_from_interval(rs.intervals[0])
61+
62+
63+
def gen_max_float_from_intervals(rs: Intervals) -> Optional[float]:
64+
if rs.empty():
65+
return None
66+
return gen_max_float_from_interval(rs.intervals[-1])
67+
68+
69+
def gen_float_from_intervals(rs: Intervals) -> Optional[float]:
70+
if rs.empty():
71+
return None
72+
r = random.choice(rs.intervals)
73+
return gen_float_from_interval(r)
74+
75+
76+
def gen_min_int_from_interval(r: Interval) -> Optional[int]:
77+
if r.empty():
78+
return None
79+
if r.lower not in [-math.inf, math.inf]:
80+
if r.lower > INT64_MAX or (r.lower == INT64_MAX and r.lower_open):
81+
return None
82+
lower: int = math.floor(r.lower) + 1 if r.lower_open else math.ceil(r.lower)
83+
lower = max(INT64_MIN, lower)
84+
if r.contains(lower):
85+
return lower
86+
return None
87+
88+
89+
def gen_max_int_from_interval(r: Interval) -> Optional[int]:
90+
if r.empty():
91+
return None
92+
if r.upper not in [-math.inf, math.inf]:
93+
if r.upper < INT64_MIN or (r.upper == INT64_MIN and r.upper_open):
94+
return None
95+
upper: int = math.ceil(r.upper) - 1 if r.upper_open else math.floor(r.upper)
96+
upper = min(INT64_MAX, upper)
97+
if r.contains(upper):
98+
return upper
99+
return None
100+
101+
102+
def gen_int_from_interval(r: Interval) -> Optional[int]:
103+
if r.empty():
104+
return None
105+
lower = gen_min_int_from_interval(r)
106+
upper = gen_max_int_from_interval(r)
107+
if lower is None and upper is None:
108+
lower = -BOUND_ON_INF
109+
upper = BOUND_ON_INF
110+
elif lower is None:
111+
lower = min(0, upper) - BOUND_ON_INF
112+
elif upper is None:
113+
upper = max(lower, 0) + BOUND_ON_INF
114+
assert lower is not None and upper is not None
115+
return random.randint(lower, upper)
116+
117+
118+
def gen_min_int_from_intervals(rs: Intervals) -> Optional[int]:
119+
for r in rs.intervals:
120+
if r.contains_int():
121+
return gen_min_int_from_interval(r)
122+
return None
123+
124+
125+
def gen_max_int_from_intervals(rs: Intervals) -> Optional[int]:
126+
for r in reversed(rs.intervals):
127+
if r.contains_int():
128+
return gen_max_int_from_interval(r)
129+
return None
130+
131+
132+
def gen_int_from_intervals(rs: Intervals) -> Optional[int]:
133+
intervals_with_ints = [r for r in rs.intervals if r.contains_int()]
134+
if len(intervals_with_ints) == 0:
135+
return None
136+
r = random.choice(intervals_with_ints)
137+
return gen_int_from_interval(r)
138+
139+
140+
class VariableGenerator:
141+
"""
142+
A variable generator needs to be initialized with a variable space.
143+
It is equipped with methods to generate values from that variable space.
144+
"""
145+
146+
def __init__(self, space: VariableSpace):
147+
self.vtype = space.vtype
148+
self.space = space
149+
150+
def gen_min(self) -> Any:
151+
"""Returns the minimum value of the space."""
152+
if self.space.empty() or self.vtype not in [bool, int, float]:
153+
return None
154+
elif self.space.discrete.initialized:
155+
return min(self.space.discrete.values)
156+
elif self.vtype == int:
157+
return gen_min_int_from_intervals(self.space.intervals)
158+
elif self.vtype == float:
159+
return gen_min_float_from_intervals(self.space.intervals)
160+
else:
161+
raise Exception("Impossible path")
162+
163+
def gen_max(self) -> Any:
164+
"""Returns the maximum value of the space."""
165+
if self.space.empty() or self.vtype not in [bool, int, float]:
166+
return None
167+
elif self.space.discrete.initialized:
168+
return max(self.space.discrete.values)
169+
elif self.vtype == int:
170+
return gen_max_int_from_intervals(self.space.intervals)
171+
elif self.vtype == float:
172+
return gen_max_float_from_intervals(self.space.intervals)
173+
else:
174+
raise Exception("Impossible path")
175+
176+
def gen_extremes(self) -> Set[Any]:
177+
"""Returns the extreme values of the space."""
178+
if self.space.empty() or self.vtype not in [bool, int, float]:
179+
return set()
180+
elif self.space.discrete.initialized:
181+
return {min(self.space.discrete.values), max(self.space.discrete.values)}
182+
elif self.vtype == int:
183+
return {
184+
gen_min_int_from_intervals(self.space.intervals),
185+
gen_max_int_from_intervals(self.space.intervals),
186+
} - {None}
187+
elif self.vtype == float:
188+
return {
189+
gen_min_float_from_intervals(self.space.intervals),
190+
gen_max_float_from_intervals(self.space.intervals),
191+
} - {None}
192+
else:
193+
raise Exception("Impossible path")
194+
195+
def gen_edges(self) -> Set[Any]:
196+
"""Returns the edge values of the space. An edge is an interval boundary."""
197+
edge_vals: Set[Any] = set()
198+
if self.space.empty() or self.space.discrete.initialized:
199+
pass
200+
elif self.vtype == int:
201+
for r in self.space.intervals.intervals:
202+
if r.contains_int():
203+
lower = gen_min_int_from_interval(r)
204+
if lower is not None:
205+
edge_vals.add(lower)
206+
upper = gen_max_int_from_interval(r)
207+
if upper is not None:
208+
edge_vals.add(upper)
209+
elif self.vtype == float:
210+
for r in self.space.intervals.intervals:
211+
edge_vals.add(gen_min_float_from_interval(r))
212+
edge_vals.add(gen_max_float_from_interval(r))
213+
else:
214+
raise Exception("Impossible path")
215+
return edge_vals
216+
217+
def gen_edges_non_extreme(self, num: int = 2) -> Set[Any]:
218+
"""Generates edge values that are not extremal."""
219+
if self.space.empty() or self.space.discrete.initialized:
220+
return set()
221+
edges_not_extreme = self.gen_edges() - self.gen_extremes()
222+
if num >= len(edges_not_extreme):
223+
return edges_not_extreme
224+
return set(random.sample(list(edges_not_extreme), num))
225+
226+
def gen_non_edges(self, num: int = 2) -> Set[Any]:
227+
"""Generates non-edge (or interior) values of the space."""
228+
if self.space.empty() or self.vtype == bool:
229+
return set()
230+
edge_or_extreme_vals = self.gen_edges() | self.gen_extremes()
231+
vals = set()
232+
if self.space.discrete.initialized:
233+
vals = self.space.discrete.values - edge_or_extreme_vals
234+
if num < len(vals):
235+
vals = set(random.sample(list(vals), num))
236+
else:
237+
for _ in range(100):
238+
v: Optional[Union[int, float]] = None
239+
if self.vtype == int:
240+
v = gen_int_from_intervals(self.space.intervals)
241+
else:
242+
v = gen_float_from_intervals(self.space.intervals)
243+
if v is None:
244+
continue
245+
v = self.vtype(v)
246+
if v not in edge_or_extreme_vals:
247+
vals.add(v)
248+
if len(vals) >= num:
249+
break
250+
return vals
251+
252+
def gen_balanced(self, num: int = 6) -> Set[Any]:
253+
"""Generates a balanced sample of the space. Balanced, in the sense that
254+
extremal values, edge values, and interior values are drawn as equally likely
255+
as possible."""
256+
if self.space.empty():
257+
return set()
258+
extreme_vals = self.gen_extremes()
259+
260+
if self.space.discrete.initialized:
261+
num2 = max(2, num - len(extreme_vals))
262+
interior_vals = self.gen_non_edges(num2)
263+
balanced = extreme_vals | interior_vals
264+
else:
265+
num2 = max(2, math.ceil((num - len(extreme_vals)) / 2))
266+
edge_non_extreme_vals = self.gen_edges_non_extreme(num2)
267+
interior_vals = self.gen_non_edges(num2)
268+
balanced = extreme_vals | edge_non_extreme_vals | interior_vals
269+
270+
if num >= len(balanced):
271+
return balanced
272+
return set(random.sample(list(balanced), num))
273+
274+
def gen(self, num: int = 6) -> List[Any]:
275+
"""Generates a sorted (if applicable), balanced sample of the space."""
276+
vals = list(self.gen_balanced(num))
277+
if self.vtype in [bool, int, float, str]:
278+
return sorted(vals)
279+
return vals

inputgen/variable/utils.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
import math
8+
import struct
9+
import sys
10+
11+
12+
def nextup(x):
13+
"""Return the next floating-point value after x towards infinity."""
14+
15+
# Check the Python version
16+
if sys.version_info >= (3, 9):
17+
# Use the built-in math.nextafter for Python 3.9 and later
18+
return math.nextafter(x, math.inf)
19+
20+
if math.isnan(x) or (math.isinf(x) and x > 0):
21+
return x
22+
x = 0.0 if x == -0.0 else x
23+
n = struct.unpack("<q", struct.pack("<d", x))[0]
24+
n = n + 1 if n >= 0 else n - 1
25+
return struct.unpack("<d", struct.pack("<q", n))[0]
26+
27+
28+
def nextdown(x):
29+
"""Return the next floating-point value after x towards -infinity."""
30+
return -nextup(-x)

0 commit comments

Comments
 (0)