Source code for pops.codegen.loader

"""pops.codegen.loader : result/wrapper classes for compiled .so artefacts.

``CompiledModel`` packages a model ``.so`` with the metadata needed to wire
it (adder, names, roles, gamma, n_aux, params, caps, abi_key, model_hash).

``CompiledProblem`` packages a program ``.so`` (a compiled time
``pops.time.Program``) plus the metadata to install + reproduce it.

Neither class imports ``pops.dsl`` or ``pops.physics`` at module level.
"""


[docs] class CompiledProblem: """Result of ``pops.compile_problem(...)``; a generated ``problem.so`` (a compiled time Program) plus the metadata to install + reproduce it. Install it with ``sim.install_program(compiled.so_path)`` AFTER the physical block has been added (``sim.add_equation`` / ``sim.add_block``); the Program then drives ``sim.step(dt)`` entirely in C++ via ``ProgramContext``. The ``.so`` is compiled against the pops headers with the SAME Kokkos toolchain as the loaded _pops module (cf. ``pops_loader_build_flags``), so its ABI key matches and ``System::install_program`` accepts it. ``os.fspath(compiled)`` returns ``so_path`` (it can be passed where a path is expected). """ def __init__(self, so_path, program, model, abi_key, cxx, std, libraries=None, problem_hash=None, cache_key=None, compile_command=None, generated_sources=None, codegen_env=None): self.so_path = so_path self.program = program # the pops.time.Program that was lowered self.model = model # the physical model (optional; added as a block in the MVP) self.program_name = getattr(program, "name", None) self.program_hash = program._ir_hash() if hasattr(program, "_ir_hash") else None self.abi_key = abi_key # cache key: header signature | compiler | C++ standard self.cxx = cxx self.std = std # Validated brick libraries (Spec 3 section 21, ADC-464): the LibraryManifests read + # ABI-checked from libraries=[...]. Empty when none were passed. Their bricks (and their # generated symbols) are exposed to the problem; a compiled library .so was already # dlopen'd (and ABI-guarded) by read_library_manifest. self.libraries = list(libraries) if libraries else [] # Compiled-artifact metadata (Spec 5 sec.12.4, #48-49): set by compile_problem. The # problem hash is the program-SOURCE hash (the WHAT the .so was built from -- distinct from # program_hash, the IR hash of the in-memory Program); cache_key is the (problem_hash|abi) # identity the out-of-source cache file name carries; compile_command is the redacted # compiler invocation; generated_sources are the .cpp files written for inspection (debug=). # None on a route that does not record a value (e.g. an externally constructed handle) -- a # documented absence, not a fabricated value (cf. the property accessors below). self._problem_hash = problem_hash self._cache_key = cache_key self._compile_command = compile_command self._generated_sources = list(generated_sources) if generated_sources else [] # Active codegen POPS_* environment snapshot (Spec 5 sec.12.4, #47-48): the resolved # CodegenEnv that governed this compile (log level, codegen dir, keep-generated, dump flags, # cache dir, profile, autotune, and the UNSAFE jit-backdoor gate). Recorded so the env state # is inspectable in inspect(); None for a handle built outside compile_problem (no env was # resolved -- a documented absence, not a fabricated default). self._codegen_env = codegen_env def __fspath__(self): return self.so_path # --- compiled-artifact metadata (Spec 5 sec.12.4, #48-49) ---------------- @property def codegen_dir(self): """Directory the compiled ``.so`` (and any generated source) lives in (sec.12.4, #48). The out-of-source cache directory the .so was written to (``os.path.dirname(so_path)``); the generated ``.cpp`` -- when ``compile_problem(debug=True)`` wrote one -- sits beside it. ``None`` only if the handle carries no ``so_path``.""" import os return os.path.dirname(self.so_path) if self.so_path else None @property def problem_hash(self): """Stable hash of the program SOURCE the ``.so`` was compiled from (sec.12.4, #48). The sha256 of the emitted C++ program text -- the cache identity (the WHAT). ``None`` for a handle built outside ``compile_problem`` (it records no source hash); use :attr:`program_hash` for the in-memory Program's IR hash, which is always available.""" return self._problem_hash @property def cache_key(self): """The (problem source | abi_key | backend/target) cache key of the ``.so`` (sec.12.4, #48). The sha256 the out-of-source build cache keys the artifact on; reproducing it requires the same program, headers, compiler and C++ standard. ``None`` for an externally built handle.""" return self._cache_key @property def compile_command(self): """The REDACTED compiler invocation that built the ``.so`` (sec.12.4, #49). A single command string with the ephemeral temp source replaced by the generated-source path and any secret-looking token masked (cf. ``_redact_compile_command``). ``None`` on a cache HIT (the .so was not rebuilt this call -- a documented absence, never a fabricated command) or for an externally constructed handle. Recompile with ``force=True`` to populate it.""" return self._compile_command @property def generated_sources(self): """The generated source files written for inspection (sec.12.4, #49). The ``.cpp`` files ``compile_problem(debug=True)`` (or ``POPS_KEEP_GENERATED``) persisted next to the ``.so`` (the default keeps the source only in a TemporaryDirectory, so this is empty unless one of those was set). A list (possibly empty), never ``None``.""" return list(self._generated_sources) @property def codegen_env(self): """The resolved codegen ``POPS_*`` environment snapshot that governed this compile (sec.12.4). A :class:`pops.codegen.env.CodegenEnv` recording the EFFECTIVE settings (env defaults already overridden by any explicit argument): log level, codegen dir, keep-generated, dump-IR / dump-CPP, cache dir, profile, autotune level, and the UNSAFE :attr:`CodegenEnv.jit_backdoor` gate. Surfaced in :meth:`inspect` so the active env state is never hidden (criterion #47). ``None`` for a handle built outside ``compile_problem``.""" return self._codegen_env
[docs] def runtime_param_routes(self): """``(per_block, defaults)`` routing the Program's RUNTIME parameters to the per-PROGRAM-block ``set_program_params`` vectors (ADC-510): per_block maps a program block index to its param names in within-block index order (matching the ``.so`` metadata + the lowered read), defaults a name to its declaration value. Built via the SAME ``program_param_entries`` the codegen emits. No bind.""" from pops.codegen.program_emit_params import program_param_routes return program_param_routes(self.program, self.model)
# --- operator introspection (Spec 2, S2-5): metadata read from the carried model, # no need to load or run the .so. def _intro_model(self): if self.model is None: raise ValueError("this CompiledProblem carries no model; operator introspection " "is unavailable") return self.model
[docs] def list_operators(self): """Names of the typed operators of the compiled module (registration order).""" return self._intro_model().operator_registry().names()
[docs] def list_state_spaces(self): """Names of the compiled module's state spaces.""" return self._intro_model().list_state_spaces()
[docs] def list_field_spaces(self): """Names of the compiled module's field spaces.""" return self._intro_model().list_field_spaces()
[docs] def operator_signature(self, name): """The pops.model.Signature of operator ``name`` in the compiled module.""" return self._intro_model().operator_registry().get(name).signature
[docs] def operator_requirements(self, name): """The requirements dict of operator ``name``.""" return dict(self._intro_model().operator_registry().get(name).requirements)
[docs] def operator_capabilities(self, name): """The capabilities dict of operator ``name``.""" return dict(self._intro_model().operator_registry().get(name).capabilities)
# --- bind-input + memory introspection (Spec 5 sec.12.2 / 12.3, #44-46) --- # These read the carried metadata (the lowered Program + the physical model); they do NOT # compile, bind, dlopen or read any runtime array.
[docs] def arguments(self): """The runtime inputs this artifact expects at ``System.install`` (Spec 5 sec.12.2, #44-45). Returns an :class:`pops.codegen.inspect_compiled.Arguments` listing -- WITHOUT any bind or runtime data -- the instances (state space / components / required), params (type / kind / required), aux (layout / required), solvers (problem / solver), outputs and the runtime layout the artifact expects. Sourced from the carried Program (the blocks it commits, the field solves it performs) and the physical model (its state / params / aux). It is DISTINCT from :meth:`requirements`-style compile constraints: ``arguments`` lists what you must SUPPLY to bind. It allocates and reads nothing.""" from pops.codegen.inspect_compiled import build_arguments return build_arguments(self)
[docs] def manifest(self): """The RICH self-describing manifest of this artifact (Spec 5 sec.13.12, #36). Returns a :class:`pops.external.CompiledArtifactManifest`: the ABI identity (``abi_key`` / ``required_headers_sig``), the model name, the blocks / variables / roles, the required aux, the const / runtime params, the ghost depth, the field outputs and the ``supports_*`` capability flags (uniform / AMR / MPI / GPU known from the backend caps; stride / partial-IMEX mask / named fields honestly ``None`` until the C++ codegen emits them). It AGGREGATES the metadata this handle already carries (via :meth:`arguments` + the carried model + ``abi_key``); it binds, dlopens and runs nothing. The widening of the thin :class:`pops.external.CompiledManifest` (a brick-id / category list) into the full artifact self-description Spec 5 sec.13.12 requires.""" from pops.external.artifact_manifest import build_compiled_manifest return build_compiled_manifest(self)
[docs] def estimate_memory(self, mesh, *, platform=None, layout=None): """A FORMULA-based memory estimate on ``mesh`` (Spec 5 sec.12.3, #46). Returns an :class:`pops.codegen.inspect_compiled.MemoryEstimate`: the state / field-output / aux / RHS-scratch / state-scratch / scalar-field / Krylov / multigrid / AMR-patch / halo / MPI-buffer byte budgets, computed as a FORMULA over the mesh shape and the artifact's static cost (``Program.estimate``) + component counts. It NEVER allocates a ``MultiFab``; every assumption is in :attr:`MemoryEstimate.assumptions` and the estimate is CONSERVATIVE. @p mesh an ``pops.mesh.CartesianMesh`` (or an int / 2-tuple of extents); @p platform an optional hint (``"mpi"`` adds the halo-exchange buffer); @p layout an optional ``pops.mesh.layouts.AMR`` / ``Uniform`` for an AMR hierarchy estimate (conservative; full-refinement worst case).""" from pops.codegen.inspect_compiled import build_memory_estimate return build_memory_estimate(self, mesh, platform=platform, layout=layout)
[docs] def scratch_plan(self): """The scratch-buffer liveness plan of this artifact's time Program (Spec 5 sec.13.11.3, #38). Returns a :class:`pops.codegen.scratch_plan.ScratchPlan`: the per-category scratch counts (state / rhs / scalar-field), the PROVABLY-reusable buffers (scratch nodes whose SSA live ranges are disjoint, so the codegen may share one buffer), the REJECTED reuse (with the reason -- a still-live occupant or an aux/field barrier) and the PERSISTENT Krylov / multigrid solver buffers. Computed by a liveness analysis over the carried Program IR (``Program.scratch_liveness`` / ``buffer_reuse_report``); the step-body reuse is EXACT, the persistent solver counts are conservative and labelled so. It NEVER binds, dlopens or allocates -- it is inspectable BEFORE ``System.install``. Raises a clear error if this handle carries no Program.""" from pops.codegen.scratch_plan import build_scratch_plan return build_scratch_plan(self._require_program("scratch_plan"), model=self.model)
# --- inspection completeness (Spec 5 sec.12.1, criterion #15) ------------- # The print(compiled) reports + the codegen/IR dumps. All INERT metadata-reading (they aggregate # the carried Program + model + compile artifacts), EXCEPT dump_cpp which REUSES the existing # emit_cpp_program codegen. None binds, dlopens, allocates or runs.
[docs] def inspect(self): """A printable :class:`pops.codegen.inspect_report.CompiledReport` of this artifact (sec.12.1). The ``print(compiled.inspect())`` summary: name, backend, platform, layout, blocks (+ state / components), fields (+ solver), program (+ commits), the REQUIRED runtime inputs (states / params / aux from :meth:`arguments`), the on-disk artifacts (so_path / abi_key / cache_key) and the bind-pending status line. It AGGREGATES the metadata this handle already carries (no compile / bind / runtime read); :meth:`~CompiledReport.to_dict` serialises it.""" from pops.codegen.inspect_report import build_compiled_report return build_compiled_report(self)
[docs] def requirements(self): """The COMPILE-TIME constraints of this artifact (sec.12.1), DISTINCT from :meth:`arguments`. Returns a :class:`pops.codegen.inspect_report.RequirementsReport`: the model capabilities the lowered route relies on (``wave_speeds`` / ``hllc_star_state`` / ``roe_dissipation``, read from the carried model's emitted flags), the required descriptors (the spatial scheme is a BIND input, reported as such), and the layout / backend / ABI constraints. A piece genuinely unknowable from today's metadata is stated honestly in :attr:`~RequirementsReport.unknown`, never fabricated. :meth:`arguments` lists what you SUPPLY at bind; ``requirements`` lists what the compiled route NEEDS from the model + toolchain.""" from pops.codegen.inspect_report import build_requirements return build_requirements(self)
[docs] def inspect_capabilities(self): """The descriptor capability rows relevant to THIS compiled artifact (sec.12.1). Delegates to the top-level :func:`pops.inspect_capabilities` machinery (the descriptor-sourced capability matrix) and SCOPES it to the descriptor categories a compiled time Program can select at bind -- the Riemann / reconstruction / limiter / projection bricks and the mesh layouts (the solver / field catalogs are bind inputs, kept too). Returns the same printable :class:`pops.CapabilityMatrix`. PURE: it imports only the inert authoring catalogs, never ``_pops`` (cf. :func:`pops.inspect_capabilities`).""" from pops._capabilities import inspect_capabilities, CapabilityMatrix matrix = inspect_capabilities() scoped = [e for e in matrix if e.category in self._CAPABILITY_CATEGORIES] return CapabilityMatrix(scoped)
# Descriptor categories a compiled time Program selects from at bind (a spatial brick + a layout # + a field solver); the capability scope of inspect_capabilities(). _CAPABILITY_CATEGORIES = ("riemann", "reconstruction", "limiter", "projection", "layout", "solver", "field")
[docs] def dump_ir(self, path=None): """Write the serialized Program IR (JSON) -- the SAME serialization ``_ir_hash`` digests. EXPOSES the existing codegen: the lowered ``pops.time.Program``'s ``_serialize()`` blob (its nodes, commits, block order, optional dt bound) as indented, sort-keyed JSON -- byte-stable run to run, the WHAT the ``.so`` was built from. Writes to @p path if given (returns the path), else returns the JSON string. Raises a clear error if this handle carries no Program.""" import json program = self._require_program("dump_ir") blob = json.dumps(program._serialize(), indent=2, sort_keys=True) if path is not None: with open(str(path), "w", encoding="utf-8") as handle: handle.write(blob) return path return blob
[docs] def dump_cpp(self, target): """Write the generated C++ source of the problem ``.so`` (REUSES the existing emit). Calls the EXISTING ``Program.emit_cpp_program(model=...)`` codegen (the same source ``compile_problem`` compiles) and writes it. @p target is a directory (the source is written as ``<program_name>.cpp`` inside it) OR a path ending in ``.cpp`` (written verbatim); the parent directory must exist. Returns the written file path. The carried model is passed so a Program whose IR names a model source / linear kernel lowers (without it such a Program raises the SAME NotImplementedError the compile path raises -- it is not faked). Raises a clear error if this handle carries no Program.""" import os program = self._require_program("dump_cpp") src = program.emit_cpp_program(model=self.model) name = self.program_name or "problem" if str(target).endswith(".cpp"): out_path = str(target) parent = os.path.dirname(out_path) or "." else: parent = str(target) out_path = os.path.join(parent, "%s.cpp" % name) if not os.path.isdir(parent): raise NotADirectoryError( "dump_cpp: the target directory %r does not exist; create it first " "(dump_cpp does not allocate or create directories)." % (parent,)) with open(out_path, "w", encoding="utf-8") as handle: handle.write(src) return out_path
[docs] def dump_schedule(self, path=None): """Write the schedule / commit order of the Program (the block advance order). EXPOSES the lowered schedule WITHOUT running it: the committed blocks in the runtime block index order (``_block_indices``: the order the Program first declares each block via ``P.state``, the order ``install_program`` binds them), each with the IR id of its committed State value. A plain, deterministic text listing. Writes to @p path if given (returns the path), else returns the string. Raises a clear error if this handle carries no Program.""" program = self._require_program("dump_schedule") commits = program.commits() order = program._block_indices() if hasattr(program, "_block_indices") else {} ordered = sorted(commits, key=lambda b: order.get(b, len(order))) lines = ["schedule for Program %r (block commit order):" % (self.program_name or "problem")] for block in ordered: state = commits[block] lines.append(" %2d commit %-14s <- %s" % (order.get(block, -1), block, getattr(state, "name", "?"))) if not ordered: lines.append(" (no committed block)") text = "\n".join(lines) if path is not None: with open(str(path), "w", encoding="utf-8") as handle: handle.write(text) return path return text
def _require_program(self, who): """Return the carried Program, or raise a clear error naming what is missing (never fake).""" program = self.program if program is None: raise ValueError( "%s: this CompiledProblem carries no Program (the lowered pops.time.Program is " "unavailable on this handle), so the IR / C++ / schedule cannot be dumped." % who) return program
[docs] def inspect_amr(self, layout=None): """STATIC AMR report on this compiled artifact (Spec 5 sec.8.12 / sec.8.4). A compiled time ``Program`` carries NO AMR layout descriptor (it lowers a whole-system time program, a single-level ``System`` concept today -- ``AmrSystem`` has no ``install_program`` seam). So this delegates to the top-level :func:`pops.inspect_amr` on an EXPLICIT ``layout`` argument (an ``pops.mesh.layouts.AMR`` / ``Uniform`` descriptor), and with ``layout=None`` returns the native AMR envelope report -- never a fabricated hierarchy the artifact does not carry. @p layout an optional AMR / Uniform layout descriptor (default: the native envelope). """ from pops import inspect_amr return inspect_amr(layout)
def __str__(self): """A short, deterministic, array-free summary (Spec 5 sec.12.1, #40-41). Prints the program name, a short program-source/IR hash, a short ABI key and the validated library count -- never the ``.so`` contents and never a ``<...object at 0x...>`` repr. The hash makes two artifacts of the same program look identical run to run (deterministic).""" short_hash = (self._problem_hash or self.program_hash or "")[:12] or "none" short_abi = (self.abi_key or "")[:12] or "none" return ("CompiledProblem(name=%s, hash=%s, backend=%s, libraries=%d)" % (self.program_name or "problem", short_hash, short_abi, len(self.libraries))) def __repr__(self): return "<CompiledProblem %r -> %s>" % (self.program_name, self.so_path)
[docs] class CompiledModel: """Result of ``m.compile(...)``: packages the produced ``.so`` + EVERYTHING needed to wire it correctly (dispatch adder, ABI diagnostic, reproducibility). Replaces the historical pair (str so_path, adder_for(backend)) with a single object. The metadata is NOT re-read from the ``.so``: Python already holds names/roles/gamma/n_aux/params (the HyperbolicModel carries them); CompiledModel just exposes them for dispatch (add_equation) and diagnostics. cf. DSL_MODEL_DESIGN.md section 3. """ def __init__(self, so_path, backend, adder, cons_names, cons_roles, prim_names, n_vars, gamma, n_aux, params, caps, abi_key, model_hash, cxx, std, target="system", hllc=False, roe=False, aux_extra_names=None, wave_speeds=False, elliptic_field_names=None): self.has_hllc = bool(hllc) # HLLC capability emitted (enable_hllc): hllc available beyond 4-var Euler self.has_roe = bool(roe) # ROE hook emitted (enable_roe roles OR m.roe_dissipation provided): roe available beyond 4-var Euler self.has_wave_speeds = bool(wave_speeds) # wave_speeds emitted (explicit pair OR 'p'): hll available self.so_path = so_path self.backend = backend # "prototype" | "aot" | "production" self.target = target # "system" | "amr_system": targeted facade (native AMR loader if amr_system) self.adder = adder # method name (Amr)System: add_dynamic_block / add_compiled_block / add_native_block self.cons_names = list(cons_names) self.cons_roles = list(cons_roles) self.prim_names = list(prim_names) self.n_vars = int(n_vars) self.gamma = gamma # None = historical default 1.4 on the System side self.n_aux = int(n_aux) # Names of the NAMED aux fields (aux_field, ADC-70), ORDERED: component index = position # AUX_NAMED_BASE + k. The System.add_equation facade builds the name -> component table per # block from it, consumed by System.set_aux_field / aux_field. Empty for a model without a named field. self.aux_extra_names = list(aux_extra_names) if aux_extra_names else [] # Names of the model's NAMED elliptic fields (m.elliptic_field, ADC-419 / ADC-428): each is a # second-or-further elliptic solve the native loader wires via register_elliptic_field + # set_block_elliptic_field after the block is installed. The install seam consults this set to # decide whether a bind(solvers={field: ...}) selection names a DECLARED field (route it) or a # typo (reject, naming the declared set). Empty for a model with only the default Poisson field. self.elliptic_field_names = list(elliptic_field_names) if elliptic_field_names else [] self.params = dict(params) # {name: Param} self.caps = dict(caps) # {cpu/mpi/amr/gpu: bool} self.abi_key = abi_key # ABI key mirroring pops_header_signature + compiler/std self.model_hash = model_hash # stable hash formulas+roles+n_aux+params self.cxx = cxx self.std = std @property def runtime_param_names(self): """Names of the model's RUNTIME parameters (kind='runtime'), SORTED: this is the ORDER of the indices on the C++ side (RuntimeParams) AND the order expected by System.set_block_params(name, values) (P7-b). Empty if the model has only const params.""" return sorted(k for k, p in self.params.items() if getattr(p, "kind", "const") == "runtime")
[docs] def runtime_param_values(self): """DECLARATION values of the runtime params, parallel to runtime_param_names (default as long as no set_block_params has been called).""" return [self.params[k].value for k in self.runtime_param_names]
[docs] def check_runtime(self, n=16, state=None, raise_on_error=True, rtol=1e-8, atol=1e-10): """RUNTIME re-verification of a CompiledModel ALONE (audit balance, GENERICITY pt 9): without the original dsl.Model, the FORMULAS are no longer re-verifiable (symbolic check_model), but the .so itself is -- we install it in an EPHEMERAL System (n x n periodic, neutral Poisson, minmod+rusanov) and delegate to System.check_model (finite state, residual -div F + S finite, positivity by roles, round-trip of THE MODEL conversions). @p state: dict {conservative variable name: ndarray (n, n)} to control the tested state. None -> SMOKE state by ROLES (Density = 1 + gaussian bump, Momentum* = 0, Energy = 2.5, other components = 0.5) -- enough to exercise flux/source/conversions; provide state= for a precise physical regime. @return the dict from System.check_model. """ import numpy as np # lazy: only needed at check_runtime call time if getattr(self, "target", "system") != "system": raise ValueError( "CompiledModel.check_runtime: only target='system' is re-verifiable in an " "ephemeral System; a target='amr_system' loader is checked installed in its " "AmrSystem (AMR test invariants), not in isolation.") from pops import System, FiniteVolume, Explicit # lazy: avoids a top-level runtime import from pops.numerics.reconstruction.limiters import Minmod from pops.numerics.riemann import Rusanov sim = System(n=int(n), L=1.0, periodic=True) sim.set_poisson() sim.add_equation("check", model=self, spatial=FiniteVolume(limiter=Minmod(), riemann=Rusanov()), time=Explicit()) x = (np.arange(n) + 0.5) / float(n) X, Y = np.meshgrid(x, x, indexing="xy") bump = 1.0 + 0.3 * np.exp(-40.0 * ((X - 0.5) ** 2 + (Y - 0.5) ** 2)) comps = [] for name, role in zip(self.cons_names, self.cons_roles, strict=True): if state is not None and name in state: comps.append(np.asarray(state[name], dtype=float).reshape(n, n)) elif role == "Density": comps.append(bump) elif role in ("MomentumX", "MomentumY"): comps.append(np.zeros((n, n))) elif role == "Energy": comps.append(2.5 + 0.0 * bump) else: comps.append(0.5 + 0.0 * bump) sim._s.set_state("check", np.stack(comps).ravel()) return sim.check_model("check", raise_on_error=raise_on_error, rtol=rtol, atol=atol)
[docs] def inspect_amr(self, layout=None): """STATIC AMR report on this compiled MODEL (Spec 5 sec.8.12 / sec.8.4). A ``CompiledModel`` is a per-block physics ``.so``; ``target='amr_system'`` means it is loadable on the native AMR hierarchy (``add_equation``), but the model carries NO layout descriptor (the levels / ratio / regrid policy belong to the ``AmrSystem`` it is installed in, not to the model). So this delegates to the top-level :func:`pops.inspect_amr` on an EXPLICIT ``layout`` argument and returns the native AMR envelope report for ``layout=None``; for a target='system' model the report still describes the native AMR envelope (a model is AMR-installable only with target='amr_system'). It never fabricates a hierarchy. @p layout an optional AMR / Uniform layout descriptor (default: the native envelope). """ from pops import inspect_amr return inspect_amr(layout)
def __repr__(self): return ("CompiledModel(backend=%r, target=%r, so_path=%r, n_vars=%d, gamma=%r, n_aux=%d, " "adder=%r, runtime_params=%r, abi_key=%.12s..., model_hash=%.12s...)" % (self.backend, self.target, self.so_path, self.n_vars, self.gamma, self.n_aux, self.adder, self.runtime_param_names, self.abi_key or "", self.model_hash or ""))