ESBMC
Efficient SMT-based Context-Bounded Model Checker
Loading...
Searching...
No Matches
numpy_call_expr.h
Go to the documentation of this file.
1#pragma once
2
4
7#include <nlohmann/json.hpp>
8#include <functional>
9#include <optional>
10#include <utility>
11#include <vector>
12
13class symbol_id;
14class exprt;
15class typet;
17
20std::string extract_numpy_dtype_name(
21 const nlohmann::json &dtype_node,
22 const nlohmann::json &ast);
23
25nlohmann::json
26numpy_literal_with_dtype(const nlohmann::json &call, const nlohmann::json &ast);
27
29{
30public:
32 const symbol_id &function_id,
33 const nlohmann::json &call,
34 python_converter &converter);
35
37
38 exprt get() override;
39
40 // The construction site's own entry point (function_call_builder::build):
41 // applies try_hoist_call_arg_for_view_method ahead of get() the same way
42 // get() itself used to, but from outside the class so the check's own
43 // decision point is attributed to this small new function instead of
44 // inflating get()'s already far-over-threshold decision count (see
45 // numpy_call_expr.cpp for the full rationale).
46 static exprt build_result(
47 const symbol_id &function_id,
48 const nlohmann::json &call,
49 python_converter &converter);
50
51private:
52 exprt create_expr_from_call();
53
54 // transpose()/flatten()/ravel() and every other descriptor-materialized
55 // dispatch (sum/mean/min/max/argsort/searchsorted) over a raw Call
56 // argument (e.g. `np.eye(3).transpose()`, rewritten to
57 // `np.transpose(np.eye(3))`): hoists it into a temp so the rest of get()
58 // sees the already-correct Name case. nullopt (not this shape, or
59 // hoisting declined) when get()'s normal dispatch should run unchanged.
60 // See numpy_call_expr.cpp for the full rationale, including which methods
61 // had to stay excluded (their own Name-argument resolution walks the
62 // source AST rather than the descriptor map, so it can't see a temp that
63 // exists only in the GOTO IR).
64 std::optional<exprt>
65 try_hoist_call_arg_for_view_method(const std::string &function);
66
67 // The array a reducer receives, when only a name can carry it (see
68 // is_hoistable_array_arg), evaluated once into a named temporary that the
69 // reducer reads like any bound array. nullopt for any other argument.
70 std::optional<exprt> try_hoist_array_arg();
71 bool is_hoistable_array_arg(const nlohmann::json &call) const;
72 static exprt reduce_scalar(const std::string &function, const exprt &value);
73
74 // A dispatch whose result shape the static annotator cannot model (e.g.
75 // ravel()/flatten(), always 1-D of size = product of the input's dims)
76 // reaches an assignment target still carrying the annotator's guess --
77 // typically Any/void* -- instead of the concrete type just computed.
78 // Retyping the target in place here, the same fixup transpose's own
79 // dispatch applies inline (try_transpose_name_arg), is what makes an
80 // assignment store the array value directly instead of decaying it to a
81 // pointer to match the stale declared type; every subsequent subscript on
82 // it would otherwise resolve as a symbolic NONDET rather than the actual
83 // element. A separate function (not inlined into get()'s own flatten/
84 // ravel branch) so this decision point is attributed here instead of
85 // adding to get()'s already far-over-threshold count.
86 static exprt
87 retype_current_lhs_and_return(python_converter &converter, exprt value);
88
89 // np.arange(...) with constant, small arguments materialized to a literal
90 // list, falling back to the operational model for genuinely non-constant
91 // arguments (see numpy_call_expr.cpp for the full rationale). Split out of
92 // get() to keep that function's own decision count from growing further.
93 exprt get_arange_expr();
94
95public:
99 static std::optional<nlohmann::json> arange_literal(
100 const nlohmann::json &call,
101 const std::string &function,
102 const nlohmann::json &ast);
103
104private:
105 bool binds_static_array(const nlohmann::json &list) const;
106 long long eye_diagonal_offset(const std::string &function) const;
107 static std::size_t eye_dimension(const nlohmann::json &node);
108 nlohmann::json eye_literal(
109 const std::string &function,
110 std::size_t rows,
111 std::size_t cols) const;
112 exprt full_size_temporary(exprt size_expr);
113 exprt literal_constructor_expr(const nlohmann::json &list);
114 // The element type of the array built from the literal @p list: a fixed-width
115 // string, or empty for the literal's own.
116 typet constructor_element_type(const nlohmann::json &list) const;
117 void reject_complex_array_operands(const std::string &function) const;
118 bool is_complex_operand(const nlohmann::json &arg) const;
119 // @p build run with constructor_element_type(@p list) at the leaves of
120 // the static arrays it builds.
121 exprt with_element_type(
122 const nlohmann::json &list,
123 const std::function<exprt(const nlohmann::json &)> &build);
124 exprt typed_static_array(const nlohmann::json &list, const typet &shape);
125 exprt full_of_runtime_fill(const std::vector<std::size_t> &dims);
126 // The points of this linspace() call as a literal list.
127 nlohmann::json linspace_literal(
128 const nlohmann::json &start_node,
129 const nlohmann::json &stop_node,
130 const nlohmann::json &num_node) const;
131
132 bool is_math_function() const;
133
134 void broadcast_check(const nlohmann::json &operands) const;
135
136 std::string get_dtype() const;
137 typet get_typet_from_dtype() const;
138 size_t get_dtype_size() const;
139
140 // `*_like`'s element type from dtype= (an override) or the base array's
141 // own type. Split out of get() to keep that function's own decision count
142 // from growing further.
143 typet resolve_like_element_type(const typet &base_type);
144
145 // full()/eye()/identity()/linspace()'s literal list, cast to a dtype=
146 // keyword the same way np.array()'s own dispatch already does
147 // (cast_numpy_literal_to_dtype); a no-op when dtype= is absent. Split out
148 // of get() to keep that function's own decision count from growing
149 // further.
150 nlohmann::json apply_constructor_dtype(nlohmann::json node);
151
152 // Looks up a keyword argument by name in the call's "keywords" array (e.g.
153 // offset=/axis1=/dtype=), or nullptr if absent. Shared by every 2-D-only
154 // view/reduction dispatch (diagonal/trace/fill_diagonal) that rejects
155 // out-of-scope keyword arguments.
156 const nlohmann::json *find_keyword_arg(const std::string &name) const;
157
158 void reject_invalid_eye_identity_args(const std::string &function) const;
159
160 nlohmann::json eye_column_arg_or_default(
161 const std::string &function,
162 const nlohmann::json &default_arg) const;
163
164 // Routes to the below for "diagonal"/"trace"/"fill_diagonal"/"ravel",
165 // std::nullopt otherwise. Split out of get() for the same reason as
166 // get_arange_expr(): keeping that function's own decision count from
167 // growing further as ADR-NP-003 etapa 2 lands more pointer-view call
168 // forms.
169 std::optional<exprt> try_get_pointer_view_call_result();
170
171 // Literal offset= for diagonal/trace, shared since both accept it as
172 // either the 2nd positional argument or the offset= keyword and reject
173 // anything non-constant the same way. error_context names the caller
174 // ("diagonal"/"trace") in the thrown TypeError.
175 long long extract_literal_diagonal_offset(const char *error_context);
176
177 exprt handle_diagonal_call();
178 exprt handle_trace_call();
179 exprt handle_fill_diagonal_call();
180 std::optional<exprt> handle_ravel_pointer_view_attempt();
181 exprt handle_axis_permutation_view_call(const std::string &function);
182
183 exprt return_retyped_or_temp(exprt value);
184
185 exprt build_default_axis_transpose_expr(
186 const exprt &source_expr,
187 const std::string &error_message);
188
189 // numpy.transpose()'s parameter-shaped fast path: when `t` (the single-
190 // pointer-unwrapped type of `arg`) isn't a fully nested 2-D array -- most
191 // commonly a 2-D parameter, whose C-ABI row-pointer decay
192 // (register_function_argument) loses the outer dimension -- rebuilds a
193 // genuine nested array from the parameter's tracked full shape
194 // (numpy_param_shapes_) and returns the transposed value directly.
195 // nullopt for anything the descriptor materialization declines (rank 1,
196 // non-2-D, or not a tracked array at all), leaving the caller's own
197 // fully-nested-array handling (e.g. a local array) unchanged. Split out of
198 // create_expr_from_call to keep that function's own decision count down.
199 std::optional<exprt>
200 try_transpose_decayed_2d_param(const nlohmann::json &arg, typet t);
201
202 // The full body of create_expr_from_call's `function == "transpose"`
203 // dispatch over a resolved Name argument: the parameter-shaped fast path
204 // above, the fully-nested 2-D case (materializing via a C-call or a
205 // direct value depending on whether current_lhs exists yet), and the
206 // already-1-D passthrough. nullopt when none apply, so the caller falls
207 // through to the shared list_arg handling below unchanged. Split out of
208 // create_expr_from_call to keep that function's own decision count down.
209 std::optional<exprt>
210 try_transpose_name_arg(const nlohmann::json &arg, const exprt &arg_expr);
211
212 // np.argsort()/np.searchsorted()/np.sort()'s full dispatch bodies (axis/
213 // keyword parsing, the axis-aware/view-aware descriptor path, and the
214 // literal-only fallback). Each always returns or throws, never falls
215 // through, so get() calling it is a one-for-one replacement of its own
216 // former `if (function == "...")` body -- moving that body's decision
217 // count out of get() to keep that function's own count from growing.
218 exprt handle_argsort_call();
219 exprt handle_searchsorted_call();
220 exprt handle_sort_call();
221
222 // numpy.argsort()'s axis argument: positional (2nd arg) or axis= keyword,
223 // never both. Split out of handle_argsort_call to keep that function's
224 // own decision count down.
225 const nlohmann::json *resolve_argsort_axis_node() const;
226
227 // numpy.sort()'s axis argument: positional (2nd arg) or axis= keyword,
228 // never both; None flattens, otherwise a literal integer, or throws.
229 // Split out of handle_sort_call to keep that function's own decision
230 // count down.
231 void parse_sort_axis_and_keywords(bool &flatten, long long &axis);
232 exprt handle_broadcast_to_call();
233 std::optional<exprt>
234 try_build_nditer_descriptor_list(const nlohmann::json &arg);
235 std::vector<std::size_t>
236 resolve_reshape_shape(std::optional<std::size_t> total) const;
237 std::optional<exprt> try_build_reshape_pointer_view();
238 std::optional<exprt> reshape_strided_view(
239 python_list &list,
240 const python_list::strided_view_desc &strided,
241 const nlohmann::json &arg,
242 bool as_view) const;
243 static bool
244 axes_are_symbolic(const std::vector<python_list::strided_axis> &axes);
245 bool describe_view_operand(
246 const nlohmann::json &arg,
247 python_list &list,
248 std::optional<python_list::strided_view_desc> &source);
249 exprt emit_strided_result(
250 python_list &list,
251 const python_list::strided_view_desc &source,
252 const std::vector<python_list::strided_axis> &axes,
253 bool readonly);
254 std::optional<exprt> try_build_view_flatten_copy();
255 std::optional<exprt> try_build_axis_permutation_view();
256 std::optional<exprt> try_build_shape_only_view();
257 std::optional<exprt> try_materialize_descriptor_copy_call();
258 std::optional<exprt>
259 try_materialize_descriptor_array_call(nlohmann::json &array_arg);
260 std::optional<exprt> try_reduce_descriptor_call(const std::string &function);
261 std::optional<exprt> try_reduce_descriptor_call_along_axis(
262 const std::string &function,
263 const std::pair<std::vector<std::size_t>, std::vector<exprt>>
264 &materialized);
265 void reject_unsupported_nditer_keywords(const nlohmann::json &arg) const;
266 bool is_runtime_layout_view(const nlohmann::json &arg) const;
267 exprt get_nditer_list(const nlohmann::json &arg);
268 exprt scalar_binary_call(const std::string &function, bool allow_numpy_fold);
269 exprt lapack_value(double exact, double radius);
270 exprt numpy_math_value(const std::string &function, double x);
271 std::optional<exprt> try_numpy_math_constant();
272 std::optional<exprt> try_runtime_math(const std::string &function);
273 const std::string &canonical_function();
274 std::optional<exprt>
275 copy_of_view(const std::string &function, const exprt &operand);
276 void
277 reject_non_numeric_elements(const std::string &function, const typet &type);
278 exprt bind_array_once(const exprt &array);
279 exprt runtime_math_array(const std::string &function, const exprt &value);
280 exprt numpy_math_list(const std::string &function, const nlohmann::json &arg);
281 std::vector<std::vector<double>>
282 linalg_matrix(const std::function<void(nlohmann::json &)> &unwrap);
284 exprt eigenvalues(const std::vector<std::vector<double>> &m);
285 std::vector<exprt> eigenvalue_pair(const std::vector<std::vector<double>> &m);
286 exprt singular_values(const std::vector<std::vector<double>> &m);
287 exprt lapack_vector(const std::vector<double> &exact, double radius);
288 exprt
289 lapack_matrix(const std::vector<std::vector<double>> &exact, double radius);
290 void report_integer_overflow();
291 typet scalar_dtype_result();
292 std::optional<exprt> fold_scalar_binary(
293 const std::string &function,
294 const numeric_value &lhs,
295 const numeric_value &rhs,
296 const typet &t);
297 exprt unfolded_scalar_binary(
298 const std::string &function,
299 const numeric_value &lhs,
300 const numeric_value &rhs,
301 const typet &t);
302 void reject_unsupported_transpose_axes_rank(const std::string &function);
303
304 // sum/prod/min/max/mean/argmin/argmax's flattened fallback path (a
305 // genuine inline literal, or argmin/argmax, which never go through
306 // try_reduce_descriptor_call's own keyword check). Split out of get() for
307 // the same reason as get_arange_expr()/try_get_pointer_view_call_result().
308 void reject_unsupported_flattened_reducer_keywords(
309 const std::string &function) const;
310
311 // argmin/argmax's own axis= handling: they have no descriptor-call fast
312 // path (try_reduce_descriptor_call only covers sum/mean/min/max), so this
313 // is checked directly against the already-resolved array node, ahead of
314 // reject_unsupported_flattened_reducer_keywords's generic "no keywords at
315 // all" rejection. Returns nullopt (no axis given) to fall through to that
316 // existing flattened path unchanged.
317 std::optional<exprt> try_argmin_argmax_along_axis(
318 const std::string &function,
319 const nlohmann::json &arg);
320
321 // One-line dispatch guard so adding argmin/argmax's axis fast path does not
322 // grow get()'s own decision count -- same reasoning as get_arange_expr()/
323 // try_get_pointer_view_call_result().
324 std::optional<exprt> try_argmin_argmax_axis_result(
325 const std::string &function,
326 const nlohmann::json &arg);
327
328 // sum/prod have a defined identity result over zero elements; every other
329 // flattened reducer must reject instead. Split out of get() to keep that
330 // function's own decision count from growing further.
331 exprt empty_reducer_identity_result(const std::string &function) const;
332
333 // np.any(a, ...)/np.all(a, ...) are dispatched here (is_numpy_call()
334 // routes any np.<attr>(...) call through numpy_call_expr::get() before
335 // function_call_expr's own table-driven dispatch -- the one that would
336 // otherwise reach handle_any()/handle_all() -- ever runs). One-line
337 // dispatch guard for the same reason as get_arange_expr()/
338 // try_get_pointer_view_call_result().
339 std::optional<exprt> try_any_all_result(const std::string &function);
340 std::optional<exprt> try_early_result(const std::string &function);
341
342 // Resolves median/percentile/argsort/searchsorted's array argument to a
343 // literal List node: a List stays as-is; a Name is followed to its own
344 // declaration's value only when inline_only is false AND the name has a
345 // single assignment in scope (find_var_decl() otherwise returns the first
346 // textual assignment, not the one reaching this call -- see
347 // argsort_reassigned_array_fail). Throws when the (possibly resolved)
348 // argument still isn't a literal List. A member function rather than a
349 // lambda local to get() so its own decision count is attributed here
350 // instead of inflating get()'s.
351 nlohmann::json resolve_literal_numpy_array_input(
352 nlohmann::json arr_arg,
353 const std::string &function_name,
354 bool inline_only = false);
355
356 // Resolves `raw_arg` -- a Name already bound to a concrete numpy array, or
357 // a call to a user function returning one (direct, or via a local
358 // variable) -- through the same descriptor-materialization path
359 // sort()/argsort() already use for a Name, instead of searchsorted's own
360 // AST-literal-tracing-only resolution. See numpy_call_expr.cpp for the
361 // full rationale.
362 std::optional<std::vector<exprt>>
363 resolve_searchsorted_array_via_descriptor(const nlohmann::json &raw_arg);
364
365 // sorter=argsort(<the same array>) over a descriptor-resolved array: an
366 // exprt-level stable-sort gather (bubble_sort_numpy_paired) over
367 // resolve_searchsorted_array_via_descriptor's elements, since they are
368 // index expressions into a local array and almost never compile-time
369 // constant the way a genuine AST literal's elements would be. nullopt for
370 // anything else (a different array's argsort, a literal index array, an
371 // array the descriptor path itself can't resolve). See numpy_call_expr.cpp
372 // for the full rationale.
373 std::optional<std::vector<exprt>>
374 resolve_searchsorted_sorted_values_via_descriptor(
375 const nlohmann::json &raw_arg,
376 const nlohmann::json &sorter_node,
377 const std::string &array_name);
378
379 // Evaluates `call_node` (a call to a user function) exactly once by
380 // synthesizing `<temp> = call_node` and converting it through the normal
381 // assignment pipeline, so side effects execute once and the temp's numpy
382 // array metadata is registered like any other local-array-return
383 // assignment. Returns a Name node referencing the temp; nullopt when
384 // there is no current block to emit into.
385 std::optional<nlohmann::json>
386 hoist_call_argument_into_temp(const nlohmann::json &call_node);
387
388 // handle_searchsorted_call's dispatch once its array argument resolved via
389 // resolve_searchsorted_array_via_descriptor (no sorter= given -- see that
390 // function). Split out to keep handle_searchsorted_call's own decision
391 // count down.
392 exprt handle_searchsorted_call_over_descriptor(
393 std::vector<exprt> values,
394 bool right);
395
396 // A same-shaped placeholder for handle_searchsorted_call's array argument
397 // when it is a user function call reached during a discarded type-probe
398 // pass (hoisting would evaluate it an extra time); nullopt otherwise. See
399 // numpy_call_expr.cpp for the full rationale.
400 std::optional<exprt> try_searchsorted_probe_placeholder();
401
402 // The AST-literal resolution only, declining (nullopt) rather than
403 // throwing. See numpy_call_expr.cpp for the full rationale.
404 std::optional<nlohmann::json>
405 try_resolve_searchsorted_literal_array(const std::string &function);
406
407 // resolve_searchsorted_array_via_descriptor plus the dispatch to
408 // handle_searchsorted_call_over_descriptor, as a single nullopt-on-decline
409 // step.
410 std::optional<exprt> try_searchsorted_call_over_descriptor(bool right);
411
412 // Validates `arr_arg`'s shape and applies `sorter_node`/sortedness,
413 // returning the space handle_searchsorted_call_over_literal searches. See
414 // numpy_call_expr.cpp for the full rationale.
415 nlohmann::json resolve_searchsorted_space(
416 nlohmann::json arr_arg,
417 const nlohmann::json *sorter_node,
418 const std::string &array_name);
419
420 // handle_searchsorted_call's final step over an AST-literal `search_space`.
421 exprt handle_searchsorted_call_over_literal(
422 nlohmann::json search_space,
423 bool right);
424
425 std::optional<exprt> try_searchsorted_sorter_descriptor_fallback(
426 const nlohmann::json *sorter_node,
427 const std::string &array_name,
428 bool right);
429
430 exprt finish_searchsorted_call(
431 std::optional<nlohmann::json> literal_arg,
432 const nlohmann::json *sorter_node,
433 const std::string &array_name,
434 bool right);
435
436 // np.sum(identity(x))/np.argmin(identity(x)): a reducer's argument reaches
437 // get() as a raw Call node when it is itself a nested call, never through
438 // function_call_expr's own dispatch (where try_fold_identity_array_return
439 // handles the equivalent `y = identity(x)` case). Reuses the same
440 // converter-level substitution for a pure, argument-only return (e.g.
441 // `def identity(a): return a`), inlining it to that substituted
442 // expression -- typically a bare Name the caller's own resolve_var can
443 // then resolve as usual -- so it is left unchanged for anything else
444 // (multi-statement bodies, non-argument locals, keyword calls).
445 nlohmann::json try_inline_pure_call_arg(nlohmann::json arg) const;
446};
std::string array_name(const namespacet &ns, const expr2tc &expr)
Definition array_name.cpp:4
void base_type(type2tc &type, const namespacet &ns)
Definition base_type.cpp:6
Definition toml.hpp:6607
Definition expr.h:34
function_call_expr(const symbol_id &function_id, const nlohmann::json &call, python_converter &converter)
Definition expr.cpp:151
exprt get() override
Definition numpy_call_expr.cpp:10378
~numpy_call_expr()
Definition numpy_call_expr.cpp:3289
static exprt build_result(const symbol_id &function_id, const nlohmann::json &call, python_converter &converter)
Definition numpy_call_expr.cpp:10015
numpy_call_expr(const symbol_id &function_id, const nlohmann::json &call, python_converter &converter)
Definition numpy_call_expr.cpp:3280
static std::optional< nlohmann::json > arange_literal(const nlohmann::json &call, const std::string &function, const nlohmann::json &ast)
The literal list the np.arange call builds, in its dtype, a Name among its arguments read from its de...
Definition numpy_call_expr.cpp:8434
Main converter for transforming Python AST into ESBMC's intermediate representation.
Definition python_converter.h:76
Definition python_list.h:36
Definition symbol_id.h:6
Definition type.h:9
Definition toml.hpp:1531
std::string extract_numpy_dtype_name(const nlohmann::json &dtype_node, const nlohmann::json &ast)
The normalized scalar dtype a dtype= argument names, or a specific rejection when it names none.
Definition numpy_call_expr.cpp:5095
nlohmann::json numpy_literal_with_dtype(const nlohmann::json &call, const nlohmann::json &ast)
The literal np.array(literal, dtype=...) stores, cast to its dtype.
Definition numpy_call_expr.cpp:5281
Definition module.h:12
Definition numpy_reducer_shared.h:26
A numpy array or view as a scalar base pointer plus its axes: the descriptor every N-D view is built ...
Definition python_list.h:284
_Bool nondet_bool(void)