Repository navigation
ENH: Add the POMDPs.jl extension for DiscreteDP - #405
Merged
Merged
Conversation
Implements issue #398: QuantEcon's exact solution methods (VFI/PFI/MPFI) become available to explicit-finite POMDPs.jl models, via the first package extension in the repository (weakdeps POMDPs v1 and POMDPTools v1, dual trigger). Core gains two POMDPs-independent additions: the accessor markov_chain(res) = res.mc (the uniform vocabulary over results and policies; `@inferred`-tested), and the exported function stub DiscreteDPSolver with a MethodError hint (registered in a new __init__) directing users to load POMDPs and POMDPTools. The extension ext/QuantEconPOMDPsExt.jl provides: the constructor method DiscreteDP(m::POMDPs.MDP), an index-free tabulator into the state-action pair sparse form that enumerates states(m)/actions(m), builds its own IndexMaps (whose uniqueness validation doubles as a buffer-reuse detector), computes expected rewards in the 4-argument form under weighted_iterator, encodes isterminal states as zero-reward self-loops (value exactly 0), checks closure of the state space under transitions with informative errors, and attaches state_values/action_values so results self-decode; the options-carrying solver struct DiscreteDPSolver{Algo} <: POMDPs.Solver with a single qualified method extending the core generic (zero-argument default is VFI, mirroring native solve); POMDPs.solve returning DiscreteDPPolicy <: POMDPs.Policy, which forwards action/value to the core decode functors (sharing one IndexMap) and keeps the native result reachable as policy.res with recorded solver options; and an internal, testing-grade as_mdp exposing a DiscreteDP as a POMDPs.MDP for round trips (public naming of the export direction is deferred). Tests (test/test_pomdps.jl, run last since loading POMDPs makes the bare name solve ambiguous): importer structure and expected-reward values, terminal-state encoding, cross-validation of R and Q against POMDPTools.SparseTabularMDP, validation errors (duplicate states, transitions leaving the state space), exact round trips native -> as_mdp -> importer over both formulations including random models, and solver/policy behavior including option recording. POMDPs and POMDPTools are test-target dependencies, so the regular CI matrix exercises the extension. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The companion of examples/ifp_transient_shocks.jl: the same model of https://python.quantecon.org/ifp_egm_transient_shocks.html written as a POMDPs.jl model in six interface methods, with the Gauss-Hermite quadrature and the Young lottery living inside the model's transition method (the discretization is part of the model), tabulated by the extension's importer, and solved both result-centric (DiscreteDP(hh) + native solve + DDPPolicyFunction + markov_chain figures) and in the POMDPs idiom end to end (DiscreteDPSolver, action/value, stepthrough). Relative to the native construction, everything index-shaped disappears from user code: no R/Q assembly, no -Inf convention, and no IndexMap (the importer owns the index maps; the analysis side uses enumerate). The example requires POMDPs, POMDPTools, and Plots, which are not package dependencies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The extension's docstrings (the DiscreteDP importer method, DiscreteDPSolver, DiscreteDPPolicy) are rendered on a new API page below the QuantEcon section. The docs build loads POMDPs and POMDPTools (added to the docs project) to activate the extension and passes the extension module to makedocs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tion Experimental. DiscreteDP(m::POMDPs.MDP; sparse=Val(true)) selects the tabulation formulation: Val(true) (the default) gives the state-action pair form with sparse storage, Val(false) the dense product form, constructed directly (n and the number of actions are known upfront, so the final R and Q arrays are the only allocations; no counting pass, no transient state-action pair representation). DiscreteDPSolver(algo; sparse=Val(true), ...) carries the same flag as a type parameter, so the formulation is determined in the type domain end to end. Supersedes the earlier dense-SA-storage design on this branch: targeting dense storage within the state-action pair formulation required a pair-counting pass and a storage-dispatched accumulator, while the product form is both simpler (two independent tabulation methods over a shared header) and allocation-optimal; the dense product output is verified equal to to_product_form of the sparse import. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eights through as-is Terminal states are now encoded as zero-reward self-loops under every global action, so models whose per-state action set is empty at terminal states (a natural POMDPs.jl idiom) import correctly in both storage forms; previously the sparse form emitted no pair and the dense form left an all--Inf row, and both constructors rejected the result. Regression test added for both forms. The branch skip in tabulation changes from w > 0 to iszero(w): the importer no longer silently drops negative or NaN weights, which used to alter the model (e.g. a NaN branch imported as a valid renormalized transition). Invalid weights now pass through exactly as they would in a native constructor call, whose validity contract is the caller's; the docstring records this and the Float64 normalization of imported data. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Member
Author
|
Detailed description about the implementation, generated by Claude Code Fable 5: Implementation notes
Documentation and tests
For the release notes: new feature — the 🤖 Generated with Claude Code (Claude Fable 5) |
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an optional POMDPs.jl extension that converts finite MDP models to DiscreteDP and exposes QuantEcon solvers as POMDP policies.
Changes:
- Adds POMDP model tabulation, solver, policy, and controlled-chain integration.
- Registers POMDPs/POMDPTools as weak dependencies.
- Adds tests, API documentation, and an income-fluctuation example.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
Project.toml |
Configures extension dependencies and test targets. |
ext/QuantEconPOMDPsExt.jl |
Implements the POMDP bridge. |
ext/examples/ifp_transient_shocks_pomdps.jl |
Demonstrates extension usage. |
src/QuantEcon.jl |
Exports APIs and registers an error hint. |
src/markov/ddp.jl |
Adds solver stub and Markov-chain accessor. |
test/test_pomdps.jl |
Adds extension tests. |
test/test_ddp.jl |
Tests core APIs without the extension loaded. |
test/runtests.jl |
Runs POMDP tests last. |
docs/Project.toml |
Adds documentation dependencies. |
docs/make.jl |
Loads and documents the extension. |
docs/src/api/QuantEconPOMDPsExt.md |
Adds the extension API page. |
Suppressed comments (1)
ext/QuantEconPOMDPsExt.jl:179
- This docstring is attached to a type, but it presents a constructor signature and omits the required
# Fieldssection. Document the type signature and its three stored fields; constructor usage can remain in the prose or receive a separate method docstring.
DiscreteDPSolver(algo=VFI; sparse=Val(true), max_iter=250,
epsilon=1e-3, k=20)
POMDPs.jl solver based on the `DiscreteDP` solution methods. `algo` is
one of `VFI`, `PFI`, or `MPFI`; `sparse` selects the tabulation
formulation (`Val(true)` for state-action pair form with sparse
storage, `Val(false)` for dense product form; value-typed, and carried
as a type parameter of the solver); the remaining keyword options are
those of `solve`. `POMDPs.solve(solver, m)` tabulates `m` via
`DiscreteDP(m)`, solves it, and returns a `DiscreteDPPolicy`.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The importer DiscreteDP(m::POMDPs.MDP) docstring and the core DiscreteDPSolver stub docstring gain the # Arguments and # Returns sections required by the docstring style guide. The extension's DiscreteDPSolver docstring, which presented a constructor signature while attached to the struct, is split per the guide's type/function convention: the struct docstring now shows the parametric type name with a # Fields section, and the constructor method carries the signature docstring with # Arguments and # Returns. Docs build checked (no new warnings; the interpolation warnings on the core API page pre-date this change). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
oyamad
added a commit
to QuantEcon/ContinuousDPs.jl
that referenced
this pull request
Sep 5, 2026
Add the public type ActionInterval(lo, hi) to the core (minimum, maximum, in, rand): the set-valued counterpart of a one-dimensional ContinuousActions at one state, and the representation of a feasible action set at the POMDPs.jl interface. Model import: when actions(m, s) returns an ActionInterval, the model is solved with a continuous scalar action space whose state-dependent bounds are the interval's endpoints, by the derivative-free inner solver (the general-tier kernel forces Brent); a finite actions(m) keeps the existing discrete path. Model export returns ActionInterval for continuous action spaces (replacing the ext-internal CDPActionInterval). Add ext/examples/cdp_ex_aiyagari_pomdps.jl: the Aiyagari household with the same POMDPs.jl model specification as QuantEcon/QuantEcon.jl#405, solved by collocation with continuous assets and a continuous savings choice (productivity enters through a piecewise-linear basis with nodes at its values), and aggregate capital from a long rollout. Tests: the interval-action import matches the native ContinuousActions problem solved with Brent to roundoff; action() stays within the interval; model export's interval type. Docs, README, and the agent instructions updated for the widened action requirement. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Codex (GPT-6 Astra) <codex@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
oyamad
force-pushed
the
pomdps-extension
branch
from
October 10, 2026 04:32
6ff0684 to
824a25d
Compare
test_pomdps.jl is wrapped in a module that loads the packages as a user does (using QuantEcon, POMDPs, POMDPTools), so the tests run in the user's namespace and the POMDPs exports stay out of Main. The DiscreteDPSolver stub test moves from test_ddp.jl to the top of that module, before POMDPs and POMDPTools are loaded, and is skipped if the extension is already active. The file no longer has to run last. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
oyamad
force-pushed
the
pomdps-extension
branch
from
October 10, 2026 05:03
824a25d to
81bb8c0
Compare
Check each action against a per-action state marker before evaluating its transition. Both sparse and dense tabulation now reject the first duplicate with the same state-and-action error, independently of the reward value. This fixes dense import accepting duplicates with infinite rewards and avoids waiting for the native constructor to reject sparse duplicates. Reuse one integer array throughout tabulation, without clearing the global action space at every state. Cover adjacent and nonadjacent duplicates with finite and infinite rewards, verify that duplicate transitions are never evaluated, and preserve action reuse across states in both formulations. Validation: focused POMDPs/DDP tests and the full test suite passed. Co-Authored-By: Codex (GPT-6 Astra) <codex@openai.com>
oyamad
added a commit
to QuantEcon/ContinuousDPs.jl
that referenced
this pull request
Oct 10, 2026
* ENH: Add the POMDPs.jl interface extension
ext/ContinuousDPsPOMDPsExt.jl, activated by the dual-trigger weakdeps
[POMDPs, POMDPTools]. The public surface is the solver direction:
POMDPs.solve(::CollocationSolver, m) solves any explicit-finite
POMDPs.jl MDP by collocation, wrapping the model's transition as a
general-tier _TransitionKernel (_POMDPKernel over weighted_iterator)
carried in a kernel-injected ContinuousDP, and returns a
CollocationPolicy <: POMDPs.Policy (action = policy functor, value =
value functor, native result reachable as policy.res). Requirement
checks with informative errors: finite action set; no terminal state at
any collocation node (v1 restriction; self-loop encoding is the planned
follow-up); at least one feasible action per node (models legitimately
error on infeasible pairs, which are mapped to -Inf and never
evaluated); reward arity chosen by a probe call, with the expected form
over branches as the fallback.
The model direction is internal: ext-local as_mdp (no core stub; the
public naming is deferred to a QuantEcon-owned generic) returning
CDPMDP{S,A}, with multi-dimensional states as NTuple{N,Float64} via a
statedim keyword (a zero-dependency alternative to the SVector plan),
interval action sets for continuous actions, and initialstate accepted
as a state, number, or distribution.
Tests: adapter equivalence against native solves (identical policy,
coefficients to roundoff) on a discrete-action growth model and a
2-D-state belief MDP (trimmed odu); expected-form reward; simulators
(stepthrough); requirement-check errors; model-direction conformance,
Symbol action types, statedim=2, and the exact round trip through the
POMDPs entrance. Full suite green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* TST: Add the rollout-consistency cross-check; widen node agreement
Port the two remaining test assets from the parked pomdps-interface
branch, adapted to the current adapter API: the mean discounted return
of seeded RolloutSimulator episodes must match the fitted value within
Monte Carlo error, and the policy/value node agreement is checked over
the full collocation-node sweep instead of three sample points.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* DOC: Add the POMDPs.jl odu example (both definition styles)
Counterpart of examples/cdp_ex_odu.jl: the same belief MDP defined in
the POMDPs.jl interface — as an explicit problem type and as a
QuickMDP — and solved through POMDPs.solve(CollocationSolver(basis), m).
The file is written for POMDPs.jl users and self-checks the two styles
against each other; the correspondence with the native interface is
recorded here instead:
- The native factorization (g, shocks, weights) appears pre-composed
as the transition distribution: the SparseCat support is g applied
to the quadrature nodes (`shocks`, the same vector as the native
example's) and its probabilities are the native state-dependent
callable weights(s) evaluated at the current belief.
- Acceptance becomes an explicit Deterministic branch instead of an
identity g.
- Validated against the native example: basis coefficients agree to
~1.2e-10 (the general-traversal tier vs the structured callable-
weights path), reservation wages agree to the grid point at all
probed beliefs, and a seeded rollout reproduces the same acceptance
event. The two POMDPs definition styles agree bitwise with each
other.
Placed under ext/examples/ because it needs the extension's trigger
packages (POMDPs, POMDPTools, QuickPOMDPs) plus Distributions, beyond
the ContinuousDPs ecosystem.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* DOC: Document the POMDPs.jl interface
Add docs/src/pomdps.md, leading with the solver direction: solving
explicit-finite POMDPs.jl MDPs with CollocationSolver via POMDPs.solve.
Contents: a savings-rate optimal growth QuickMDP example (validated to
reproduce the analytic constant rate), the requirement set checked at
solve time, the CollocationPolicy semantics, the belief-MDP twin of
examples/cdp_ex_odu.jl with the kernel-equivalence note, the
QuantEcon/POMDPs solve-and-simulate name-clash table, and why
CollocationSolver does not subtype POMDPs.Solver. The internal model
direction is deliberately undocumented. Add the page to the Documenter
pages and short cross-referencing sections to README.md and
docs/src/index.md. Both code examples were run end-to-end.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* FIX: Convert solver coordinates to the model's declared state type
The adapter passed the core's coordinate points -- node-row views in the multidimensional case -- directly to the model's methods, so a model whose actions/reward/transition methods are typed on statetype(m) (e.g. NTuple{2,Float64}) hit MethodErrors, or silently dispatched to the global actions(m) fallback instead of its actions(m, s) method.
States now cross the package boundary through a converter fixed once at solve time by probing the first collocation node: pass-through on exact match (zero-cost, and always the case for scalar states), the natural elementwise conversion for Tuple state types, POMDPs.convert_s when the model provides a method for the coordinate form, and the documented indexable-points contract otherwise. Every converter is a no-op on an already-converted state, so nested application is safe. Regression test: a fully typed 2-D MDP agrees exactly with its untyped QuickMDP twin.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* FIX: Reject a scalar initial state in as_mdp when statedim > 1
A Real initialstate combined with statedim > 1 fell through to the distribution branch, storing a bare number as the initial-state object, so POMDPs.initialstate(m) returned a number rather than a distribution. The malformed combination is now rejected with an informative error.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* MAINT: Tighten interface-probe error handling; refine POMDPs requirement docs
The actions(m) and reward-arity probes no longer swallow InterruptException, and the actions(m) probe now includes the original error text in its ArgumentError instead of discarding it.
Documentation refinements: terminal states must be absent from the entire basis domain, not only at the collocation nodes (which are checked; off-node states are the model's responsibility); actions(m, s) must be a subset of actions(m), since actions outside the global set are not seen by the solver; comments and docstrings now say model import/export instead of the solver/model direction wording.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* ENH: Accept continuous scalar actions through the POMDPs.jl interface
Add the public type ActionInterval(lo, hi) to the core (minimum,
maximum, in, rand): the set-valued counterpart of a one-dimensional
ContinuousActions at one state, and the representation of a feasible
action set at the POMDPs.jl interface. Model import: when
actions(m, s) returns an ActionInterval, the model is solved with a
continuous scalar action space whose state-dependent bounds are the
interval's endpoints, by the derivative-free inner solver (the
general-tier kernel forces Brent); a finite actions(m) keeps the
existing discrete path. Model export returns ActionInterval for
continuous action spaces (replacing the ext-internal CDPActionInterval).
Add ext/examples/cdp_ex_aiyagari_pomdps.jl: the Aiyagari household with
the same POMDPs.jl model specification as QuantEcon/QuantEcon.jl#405,
solved by collocation with continuous assets and a continuous savings
choice (productivity enters through a piecewise-linear basis with nodes
at its values), and aggregate capital from a long rollout.
Tests: the interval-action import matches the native ContinuousActions
problem solved with Brent to roundoff; action() stays within the
interval; model export's interval type. Docs, README, and the agent
instructions updated for the widened action requirement.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* DOC: Solve the docs' growth model with continuous actions; test QuickMDP interval actions
The documentation example now declares the feasible actions of the
familiar growth model (reward, transition, and action bounds) as an
ActionInterval through QuickMDP's function-valued `actions`, keeping the
consumption choice continuous; finite action sets are described in one
sentence. The returned-policy section covers both action forms. A test
pins the QuickMDP function-valued interval path: identical coefficients
to the explicit-type interval model.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* DOC: Define the Aiyagari example in both styles (explicit type and QuickMDP)
Add the QuickMDP definition of the household model to the example, with
its interval-valued `actions` through QuickMDP's function form, and
cross-check it against the explicit problem type (identical
coefficients), mirroring the odu example's structure.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* ENH: Accept an ActionInterval as the action space of a ContinuousDP
A constant ActionInterval is the state-independent one-dimensional
continuous action space: ContinuousActions(itv) builds it with constant
bound functions, and every ContinuousDP constructor form (keyword,
positional, copy) accepts an interval as `actions`. ActionInterval
itself stays a set at one state, not an ActionSpace.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Revert "ENH: Accept an ActionInterval as the action space of a ContinuousDP"
This reverts commit 17d29fe.
* ENH: Address Copilot review of the POMDPs.jl extension
- ActionInterval requires finite bounds
- Continuous actions require Float64 <: actiontype(m)
- as_mdp requires the weights to form a probability vector
- CollocationPolicy refreshes its policy functor after set_eval_nodes!
- Fix the index.md overview wording (finite actions or an interval)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* FIX: Accept non-vector action collections and skip zero-probability branches in the POMDPs.jl extension
- vec(collect(actions(m))): a finite collection may collect into a
matrix (e.g. a Cartesian product of tuple-valued actions)
- The POMDPs kernel skips exact-zero weights before evaluating the
payload (a SparseCat may list impossible successors where the model's
reward is non-finite or errors); negative or non-finite weights still
propagate
- rand(rng, ::ActionInterval) uses a clamped convex combination, whose
value stays finite when the interval width overflows
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* DOC: Drop the "explicit-finite" label; state finite support explicitly
"Explicit-finite MDP" was a coinage from the planning notes, not
POMDPs.jl terminology, and it became inaccurate once the extension
accepted a continuous scalar action interval besides finite action
sets: the label said finite while its own gloss said otherwise. With
a continuous state space it also invited the wrong reading.
Remove the label from the docs page, README, index.md, the extension
source, and the agent instructions, describing the accepted models
directly instead, and bring the two summaries that still said only
"finite actions" (extension header comment, agent instructions) in
line with the action-interval support.
The one requirement the label still carried, that transition
distributions have finite support, is now stated in the requirements
list of the docs page and in the POMDPs.solve docstring.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* MAINT: Reword the action-enumeration error of the POMDPs.jl extension; test it
The error raised when actions(m) cannot be collected said the solver
"requires an explicit finite action set" and then offered an
ActionInterval as the alternative, which is not finite. State what
failed instead (the action set could not be enumerated), and name
what was being collected rather than "it".
The branch was untested: add a model that defines only the
state-dependent actions(m, s) and check the error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* DOC: Note the Float64 action-type requirement; link the Aiyagari example
The continuous-action path of the POMDPs.jl interface solves and
returns Float64 actions and requires the model's action type to accept
them, which POMDPs.solve checks; the requirements list on the docs page
did not say so. Also link the Aiyagari household example shipped in
ext/examples from the page (readers of the rendered docs have no other
path to it), and reflow a paragraph left ragged by the previous edit.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* MAINT: Update QuantEcon compat to 0.20
QuantEcon 0.20 ships its own POMDPs.jl extension, which loads alongside
ours; the full suite passes with 0.20.0 resolved and both active.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Close #398
This PR adds the package extension
QuantEconPOMDPsExtconnecting ourDiscreteDPto the JuliaPOMDP ecosystem.POMDPs.jlis a package that provides a core interface for working with MDPs (Markov decision processes) and POMDPs (partially observable Markov decision processes). (DiscreteDPis a finite MDP.)POMDPsandPOMDPToolsare added as weak dependencies (weakdeps).QuantEconPOMDPsExtis activated only withusing POMDPs, POMDPTools.Features
Modeling interface
This allows us to use the POMDPs.jl interface as a simple interface for specifying a
DiscreteDPmodel (see also QuantEcon/QuantEcon.py#228). As an example, consider the Aiyagari model as described in https://julia.quantecon.org/multi_agent_models/aiyagari.html (compare the corresponding script in #402):Joining the JuliaPOMDP ecosystem
This extension exposes our solution methods through a
POMDPs.Solver(DiscreteDPSolver), so we can use tools from the JuliaPOMDP ecosystem to work with the model and the computed policy.See "Interacting with Policies" in the POMDPs.jl documentation for details on working with the
policyobject, and "Implemented Simulators" for the tools that consume it (notesimulate, likesolve, must be qualified asPOMDPs.simulate).The native
DPSolveResultremains reachable aspolicy.res.Questions
POMDPsandPOMDPToolsas weakdeps?DiscreteDPjoin the JuliaPOMDP ecosystem. Are we comfortable with that?